A tool to migrate Asana tasks from CSV export to GitHub Projects V2.
- ✅ Migrates tasks as GitHub issues
- ✅ Maps Asana sections to GitHub Project status field
- ✅ Converts Asana tags to GitHub labels
- ✅ Handles subtasks (proper GitHub sub-issue relationships)
- ✅ Preserves completed status
- ✅ Includes custom fields as metadata
- ✅ Duplicate detection by Asana GID (prevents duplicate names from colliding)
- ✅ Asana GID tracking (custom field for reliable task lookup)
- ✅ Assignee mapping (Asana names → GitHub usernames)
- ✅ Non-interactive mode with
--yesflag - ✅ Dry-run mode for preview
The tool is organized into modular components:
- models.py - Data models (MigrationStats, ProjectFields, MigrationConfig)
- asana_parser.py - Asana CSV parsing and data extraction
- github_client.py - GitHub CLI wrapper for API operations
- migrate.py - CLI entry point and migration orchestration
- utils.py - Utility functions (diagnose, cleanup, analyze)
- GitHub CLI (
gh) - Install instructions - Python 3.9+ - Uses uv for dependency management
- Authentication - Run
gh auth loginbefore using this tool - Project scope (if creating new projects) - Run
gh auth refresh -s project
- Export your Asana project:
- Export to CSV format (includes all tasks, subtasks, and metadata)
- From Asana: Project → Export/Print → CSV
- Create/select GitHub Project:
- Option A: Let the tool create a new GitHub Project with status field automatically
- Option B: Use an existing GitHub Project (V2) and ensure status field values match your Asana sections
- Configure assignee mapping (optional):
- Create
assignee_mapping.jsonin the project root to map Asana names to GitHub usernames - Example:
{ "comment": "Map Asana assignee names to GitHub usernames", "mappings": { "John Doe": "johndoe", "Jane Smith": "janesmith" } }
- Create
Before running a migration, preview the Asana CSV export to verify parsing:
./migrate.py dry-run data/export.csvThis will display:
- Summary counts (parent tasks, subtasks, sections, custom fields)
- Sections that will become GitHub Status values
- Custom field values and their colors
- Sample tasks with full details
- Subtask counts per parent
No GitHub connection required - just validates the Asana CSV export.
# Full migration with all arguments
./migrate.py migrate data/export.csv --repo owner/repo --project 1
# Test with limited tasks (first 5 parent tasks and their subtasks)
./migrate.py migrate data/export.csv --repo owner/repo --project 1 --limit 5
# Create new project automatically
./migrate.py migrate data/export.csv --repo owner/repo --project new
# Fully interactive (will prompt for CSV path, repo, and project)
./migrate.py migratecsv_path- Path to Asana CSV export file (positional, optional - will prompt if not provided)--repo- Target GitHub repository in formatowner/repo(optional)--project- GitHub Project number ornewto create a new project (optional)--labels- Label source:tags(Asana tags) ornone(no labels). Default:tags--limit- Limit migration to first N parent tasks (useful for testing). Subtasks of these parents will also be included.--yes, -y- Automatically answer yes to all prompts (non-interactive mode for automation)
Any arguments not provided will be prompted interactively.
The tool will prompt you for:
- Label configuration (which fields to use)
- If --project not specified: Create new project or use existing one
- If creating new: Project title (status field will be auto-created from Asana sections)
- If using existing: Project number
The migration runs in multiple phases:
- Parent tasks are created first with tracking of Asana GID → GitHub issue mapping
- Subtasks are created and linked as proper GitHub sub-issues to their parents
- Existing issues are linked (subtasks to parents) based on Asana GID field
- Assignees are updated based on mapping from
assignee_mapping.json
Asana GID Custom Field: The tool automatically creates and populates an "Asana GID" custom field in your GitHub Project. This field stores the unique Asana task ID and enables:
- Reliable duplicate detection (prevents tasks with duplicate names from being skipped)
- Accurate parent-child linking for subtasks
- Idempotent migrations (re-running won't create duplicates)
You'll see a preview and be asked to confirm before any changes are made (unless using --yes flag).
✅ Migrated:
- Task name → Issue title
- Task notes → Issue body
- Asana tags → GitHub labels
- Sections → Project status field
- Custom fields → Metadata in issue body
- Completed status → Closed issues
- Subtasks → Proper GitHub sub-issues with parent-child relationships
- Asana GID → Custom "Asana GID" field (automatic)
- Assignees → GitHub assignees (via
assignee_mapping.json)
❌ Not migrated:
- Comments/activity history
- File attachments (only URLs in description)
- Empty task notes
- Orphaned subtasks (parent not in export)
- Tasks with multiple project memberships (uses first section)
- Tags without color field
- Various custom field types (enum, multi_enum, text, number)
gh auth login
gh auth status
# If creating new projects, you need the project scope
gh auth refresh -s projectEnsure you have write access to the target repository.
The GitHub Project must exist and be accessible. Use the project number from the URL:
https://github.com/users/USERNAME/projects/NUMBER or https://github.com/orgs/ORGNAME/projects/NUMBER
If Asana sections don't match GitHub Project status values, the migration will fail with a clear error message. Update your GitHub Project status field options to match.
# Step 1: Preview the Asana CSV export first
./migrate.py dry-run data/my-asana-export.csv
# Step 2: Test with a small subset (5 tasks) to verify everything works
./migrate.py migrate data/my-asana-export.csv --repo johndoe/my-repo --project 1 --limit 5
# Step 3: If test looks good, run full migration
./migrate.py migrate data/my-asana-export.csv --repo johndoe/my-repo --project 1
# Step 4: Non-interactive mode (useful for automation/CI)
./migrate.py migrate data/my-asana-export.csv --repo johndoe/my-repo --project 1 --yes
# Example project URLs
# User project: https://github.com/users/johndoe/projects/1 → use --project 1
# Org project: https://github.com/orgs/acme/projects/5 → use --project 5
# Or run interactively (will prompt for CSV path, repo, and project)
./migrate.py migrateThe tool includes additional subcommands for project management:
Diagnose a GitHub Project to identify duplicates and orphaned issues:
./migrate.py diagnose --repo owner/repo --project 15Shows:
- Duplicate issues (by title or GID)
- Orphaned subtasks (issues without expected parent relationships)
- Parent-child relationship statistics
Remove duplicate issues without Asana GID from a project:
# Interactive mode (asks for confirmation)
./migrate.py cleanup --repo owner/repo --project 15
# Non-interactive mode
./migrate.py cleanup --repo owner/repo --project 15 --yesAnalyze a CSV export to show metadata before migration:
./migrate.py analyze data/export.csvShows:
- Task counts (parents, subtasks, total)
- Sections and their distribution
- Custom fields
- Assignees and task counts
- Tags
The tool logs all operations and creates a migration report:
migration_report.log- Detailed operation log- Console output - Progress and summary
- Only first section used if task belongs to multiple projects
- People-type custom fields are skipped
- Tasks without sections default to "Todo" status
MIT
