Observed since 20 Oct, 19:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Tool to sync work items and pull requests from Azure DevOps to Asana
This project aims to synchronize work items and pull requests between Azure DevOps (ADO) and Asana. It's currently in development and not ready for use. Breaking changes will occur as needed.
This guide covers everything you need to configure and run the sync tool, either locally for development or via Docker.
The application requires environment variables and a project mapping file.
Create a .env file in the root directory. You can copy the example file to get started:
cp .env.example .env
Required Variables:
ADO_PAT: Your Personal Access Token for Azure DevOps.ADO_URL: The full URL of your Azure DevOps instance (e.g., https://dev.azure.com/your-org).ASANA_TOKEN: Your Personal Access Token for Asana.ASANA_WORKSPACE_NAME: The exact name of your Asana workspace.Optional Variables:
CLOSED_STATES: Comma-separated ADO states considered closed (default: Closed,Removed,Done).THREAD_COUNT: Number of projects to sync in parallel (default: 8).MAX_DEPTH: Maximum depth of nested subtasks to synchronize (default: 3).SLEEP_TIME: Seconds to sleep between sync runs (default: 300).RUN_ONCE: Run a single sync cycle and exit with a normal process status (default: false).DRY_RUN: Compute and log planned create/update/close actions without writing to Asana or the local sync database. The log summary includes separate create/update/close counts and ADO/PR IDs for both work items and PR reviewer tasks. Because no local mappings are persisted, repeated dry runs re-evaluate the same items from scratch (default: false).SYNC_THRESHOLD: Days to continue syncing closed tasks before unmapping (default: 30).SYNCED_TAG_NAME: Asana tag appended to all synced items (default: synced).LOGLEVEL: Console log level (default: INFO).GROUP_REVIEWER_STRATEGY: How to handle ADO group/container reviewers (e.g. [Project]\Contributor). Options: ignore (default, skips them), default_user (assigns tasks to GROUP_REVIEWER_DEFAULT_USER), unassigned_task (creates an unassigned task with the group name), expand_group_members (resolves the group to individual members via the ADO Graph API and creates a task per member — requires vso.graph PAT scope).GROUP_REVIEWER_DEFAULT_USER: Asana user email, GID, or display name to assign group reviewer tasks to when GROUP_REVIEWER_STRATEGY=default_user.USER_MATCH_STRATEGY: How far to walk the ADO→Asana user matching ladder. Options: exact (exact email only), prefix (also match the email local part before the @), name (default, also match the normalized display name). An unmatched ADO assignee leaves the existing Asana assignee untouched rather than clearing it.OTEL_TRACES_SAMPLER_ARG: Trace sampling percentage for Application Insights (e.g. 0.05 = 5%, 1.0 = 100%; default: 0.05).APPINSIGHTS_LOGLEVEL: Minimum log level forwarded to Application Insights telemetry (default: WARNING).APPINSIGHTS_SAMPLE_DEBUG / APPINSIGHTS_SAMPLE_INFO: Sampling rate for DEBUG/INFO logs sent to Application Insights (default: 0.05).APPINSIGHTS_SAMPLE_WARNING / APPINSIGHTS_SAMPLE_ERROR / APPINSIGHTS_SAMPLE_CRITICAL: Sampling rate for WARNING/ERROR/CRITICAL logs (default: 1.0). These default to 100% to preserve incident visibility.The application needs to know which ADO teams map to which Asana projects. Create a projects.json file in the data/ directory:
cp data/projects.json.example data/projects.json
Mapping Structure:
[
{
"adoProjectName": "your-ado-project",
"adoTeamName": "Backend Team",
"asanaProjectName": "your-asana-project-backend"
}
]
adoProjectName: The name of your Azure DevOps project.adoTeamName: The specific team within the ADO project whose backlog you want to sync.asanaProjectName: The corresponding Asana project name.The application stores its sync state in a SQLite database at data/appdata.db. This file tracks which ADO items have been synced to Asana so that subsequent runs perform incremental updates rather than recreating everything from scratch.
Schema migrations are applied automatically on startup — no manual steps are required when upgrading. The database is currently at schema version 3. Hot-path lookups leverage SQLite JSON indexes on the ado_id, asana_gid, ado_pr_id, and reviewer_gid fields so that queries remain fast even as the sync history grows.
Important: Ensure the data/ directory is persisted across restarts (e.g. via a Docker volume or bind mount). Without this, the sync database is lost and all Asana tasks will be recreated on the next run.
You can run the sync tool either locally using uv or via Docker.
uv sync --dev
uv run python -m ado_asana_sync.sync
Alternatively, use the installed script entry point defined in pyproject.toml:
uv run ado-asana-sync
DRY_RUN=true RUN_ONCE=true uv run python -m ado_asana_sync.sync
The checked-in VS Code run profiles use the same uv workflow: they run uv sync --dev before launch and use the .venv environment that uv manages for the workspace.
In dry-run mode, expect log-only output such as create/update/close summaries. No Asana writes are sent, and no new entries are saved to data/appdata.db.
The repository includes a compose.yml file for easy deployment.
docker compose up --build
To run in the background, append -d to the command. The container will automatically pick up your .env file and mount the data/projects.json configuration.
Once running, the application will:
data/projects.json mapping.You can verify the first sync by checking your mapped Asana project for newly created tasks with the configured synced tag.
@) → normalized display name (whitespace/order/comma tolerant), controlled by USER_MATCH_STRATEGYThe system follows this logic to determine which PRs to sync:
status="active" (excludes completed/abandoned PRs)Exclusion Criteria:
See CHANGELOG.md for a detailed history of changes and new features.
This repo uses Conventional Commits to ensure the build numbering is generated correctly
The project includes a comprehensive end-to-end (E2E) test suite under tests/e2e/ that validates the complete sync workflow using mocked API endpoints and a real temporary SQLite database. These tests run automatically as part of the standard test suite.
Running E2E tests:
uv run pytest tests/e2e/ -v
What is tested:
| Scenario | Description |
|---|---|
| New work item | ADO item not in Asana → task created |
| Work item update | ADO item changed → Asana task updated |
| Work item close | ADO item removed from backlog → Asana task completed |
| Work item reopen | Closed ADO item returns to backlog → Asana task uncompleted |
| Subtask hierarchy | Parent-child ADO relations → Asana subtask hierarchy |
| Preexisting match | ADO item matches existing Asana task by name → no duplicate |
| PR open | New PR with reviewer → reviewer task created |
| PR close | PR completed/abandoned → reviewer task completed |
| PR reopen | Reactivated PR → reviewer task uncompleted |
| PR status update | Reviewer vote changed → Asana task updated |
These tests are non-destructive: they use isolated temporary directories and mocked external APIs, so they never impact real databases or Asana/ADO workspaces.
To test the application manually, you can use the following steps:
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 1.28.3 | 3 Sept 2026 | 3 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 19:44 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:ca8331a355c3313e23cc630392c081c71a9c3a16d85322b9d8b70ba6d6b3abf9