Keeps 2i2c's Asana in sync with external sources. Two independent syncs live here:
- CSH issue sync — GitHub issues labeled
CSHbecome Asana tasks. - Engagement title sync — Asana projects in the "Active Engagements" portfolio are renamed to match their HubSpot deal names.
Each project in the "Active Engagements" Asana portfolio has a HubSpot Deal URL custom field holding a HubSpot deal URL.
A daily workflow (sync-project-titles.yml, 11:00 UTC) renames each project to match its HubSpot deal name.
Deal names come from the deals_cleaned sheet of hubspot.xlsx in the hubspot-latest release of 2i2c-org/data-private, which refreshes daily at 09:00 UTC.
Projects with an empty or unparseable deal URL, or a deal ID not in the data, are skipped with a warning.
Run it locally:
node --env-file=.env scripts/sync-project-titles.js --dry-runRequired env vars (put them in a .env file):
ASANA_ACCESS_TOKEN— Asana personal access tokenCSH_SYNC_PAT— GitHub token with read access to2i2c-org/data-private
Config lives in config/asana-config.json under engagements (portfolio_gid comes from the portfolio's URL in the Asana web app).
Unit tests (npm test) cover the title-sync helpers only.
Automatically creates Asana tasks when a GitHub issue is labeled CSH (Community Success Hours) on any repo in the 2i2c-org organization.
- A contributor adds the
CSHlabel to a GitHub issue - A GitHub Actions workflow fires on the issue event
- The workflow fetches enriched metadata from the issue and its GitHub Project board (via GraphQL)
- It creates (or updates) a corresponding Asana task with mapped fields
- It comments on the GitHub issue with a link to the new Asana task
A scheduled batch sync (csh-batch-sync.yml, every 15 minutes on weekdays) catches project-board field changes that don't fire issue events.
sync-asana/ # This repo (2i2c-org/sync-asana)
├── .github/workflows/
│ ├── csh-sync.yml # Reusable workflow (workflow_call)
│ ├── csh-batch-sync.yml # Scheduled batch sync of CSH issues
│ └── sync-project-titles.yml # Daily engagement title sync
├── scripts/
│ ├── sync-to-asana.js # CSH: single-issue webhook entry point
│ ├── sync-all.js # CSH: batch entry point
│ ├── sync-issue.js # CSH: shared core sync logic
│ ├── github-metadata.js # CSH: GraphQL queries for board fields
│ ├── field-mapping.js # CSH: GitHub → Asana field mapping
│ ├── sync-project-titles.js # HubSpot → Asana project title sync
│ ├── setup-asana.js # One-time config discovery/validation
│ ├── asana-client.js # Asana REST API wrapper (shared)
│ └── __tests__/ # Unit tests (title-sync helpers)
├── config/
│ ├── asana-config.json # Asana project/field GIDs
│ └── user-mapping.json # GitHub login → Asana user GID
└── caller-workflow-template.yml # Copy into participating repos
Run the setup script to discover GIDs, validate the Asana project's custom fields, and generate config/asana-config.json:
export ASANA_ACCESS_TOKEN="your-asana-token"
npm run setup # interactive
node scripts/setup-asana.js --project-gid 1234567890 # or directThe script reports anything you still need to do (missing fields, user mapping, enum mappings).
In the 2i2c-org organization settings (Settings → Secrets and variables → Actions):
| Secret Name | Value |
|---|---|
ASANA_ACCESS_TOKEN |
Asana personal access token or service account token |
CSH_SYNC_PAT |
GitHub PAT with repo, project:read scopes (cross-repo GraphQL for the CSH sync; also needs read access to 2i2c-org/data-private for the engagement title sync) |
In every repo that should participate in CSH sync, copy caller-workflow-template.yml into .github/workflows/.
The caller is intentionally minimal — all logic lives in this central repo.
Fields from the Product and Services board (project #57) and the issue itself map to Asana as follows.
A custom field is only synced when its GID is set in config/asana-config.json; unconfigured fields are skipped.
| GitHub source | Asana target |
|---|---|
| Issue title | Task name |
| Issue body | Task notes (plain text) |
| Issue assignees | Task assignee (via config/user-mapping.json) |
| Issue URL | GitHub Issue URL text field (idempotency key) |
| Repository full name | GitHub Repo text field |
| Labels (excluding CSH) | GitHub Labels text field |
| Status / Priority / Allocation / Unplanned | Same-named single-select fields (via enum_mappings) |
| Estimate / Hours spent | Same-named number fields |
| Iteration | Iteration text field (iteration title) |
| Start date / End date | Native start_on / due_on |
| Process / SoW | Same-named text fields |
| Milestone due date | due_on (fallback) |
Before creating a task, the script searches Asana for an existing task whose GitHub Issue URL matches the issue's URL, and updates it instead of creating a duplicate.
Re-labeling or re-running the workflow is safe.