GitHub Two-Way Sync enables bidirectional synchronization between TaskWarrior tasks and GitHub issues. Changes made in either system are automatically reflected in the other, with intelligent conflict resolution.
- Bidirectional Sync: Changes sync in both directions (TaskWarrior ↔ GitHub)
- Field Mapping: 5 bidirectional fields + 7 pull-only metadata fields
- Conflict Resolution: Automatic last-write-wins strategy
- Annotation/Comment Sync: Bidirectional with prefixes to prevent loops
- Error Handling: Interactive correction for validation errors
- Batch Operations: Sync multiple tasks at once
- Profile Isolation: Each profile has independent sync state
Bidirectional (both directions):
- Description ↔ Title
- Status ↔ State (pending/started/waiting → OPEN, completed/deleted → CLOSED)
- Priority ↔ Labels (H/M/L → priority:high/medium/low)
- Tags ↔ Labels (system tags excluded)
- Annotations ↔ Comments (with prefixes)
Pull-Only (GitHub → TaskWarrior):
- Issue number → githubissue UDA
- Issue URL → githuburl UDA
- Repository → githubrepo UDA
- Author → githubauthor UDA
- Created date → entry
- Closed date → end
- Updated date → modified
-
GitHub CLI (
gh) installed and authenticated# Install (macOS) brew install gh # Authenticate gh auth login # Verify gh auth status
-
Active Workwarrior Profile
source bin/ww p-my-profile -
GitHub Repository Access
- You need write access to the repository
- Repository can be public or private
-
Create a task in TaskWarrior
task add "Implement user authentication" priority:H +feature -
Create an issue on GitHub
gh issue create --repo myorg/myproject \ --title "Implement user authentication" \ --body "Add OAuth2 authentication"
-
Enable sync (links task to issue)
# Get task ID task list # Get issue number from GitHub or gh CLI gh issue list --repo myorg/myproject # Enable sync github-sync enable 1 42 myorg/myproject # Or use shell shortcut: i enable-sync 1 42 myorg/myproject
-
Make changes and sync
# Modify task task 1 modify priority:M task 1 annotate "Started implementation" # Push changes to GitHub i push 1 # Or sync bidirectionally i sync 1
Link a TaskWarrior task to a GitHub issue.
github-sync enable <task-id> <issue-number> <repo>
i enable-sync <task-id> <issue-number> <repo>Example:
github-sync enable 42 123 myorg/myprojectWhat it does:
- Links task to GitHub issue
- Sets githubsync UDA to "enabled"
- Populates GitHub metadata UDAs
- Performs initial pull from GitHub
Unlink a task from GitHub (preserves metadata).
github-sync disable <task-id>
i disable-sync <task-id>Example:
github-sync disable 42What it does:
- Sets githubsync UDA to "disabled"
- Removes sync state
- Preserves GitHub metadata UDAs
Push task changes to GitHub.
github-sync push [task-id] [--dry-run]
i push [task-id]Examples:
# Push specific task
i push 42
# Push all synced tasks
i push
# Preview changes (when implemented)
i push --dry-runWhat it syncs:
- Task description → Issue title
- Task status → Issue state
- Task priority → Priority labels
- Task tags → Issue labels
- Task annotations → Issue comments
Pull issue changes from GitHub.
github-sync pull [task-id] [--dry-run]
i pull [task-id]Examples:
# Pull specific task
i pull 42
# Pull all synced tasks
i pullWhat it syncs:
- Issue title → Task description
- Issue state → Task status
- Priority labels → Task priority
- Issue labels → Task tags
- Issue comments → Task annotations
- Issue metadata → Task UDAs
Bidirectional sync (detects changes on both sides).
github-sync sync [task-id] [--dry-run]
i sync [task-id]Examples:
# Sync specific task
i sync 42
# Sync all synced tasks
i syncWhat it does:
- Detects changes on both sides
- Resolves conflicts using last-write-wins
- Pushes or pulls as needed
- Syncs annotations/comments bidirectionally
Display sync status for all synced tasks.
github-sync status
i sync-statusOutput:
Task: abc123de
Description: Implement user authentication
GitHub: myorg/myproject#42
Last sync: 2024-01-15T10:30:00Z
Total synced tasks: 5
Display help information.
github-sync help [command]Examples:
# General help
github-sync help
# Command-specific help
github-sync help push
github-sync help enableConfiguration is stored per-profile at:
$WORKWARRIOR_BASE/.config/github-sync/config.sh
# Default repository (optional)
GITHUB_SYNC_DEFAULT_REPO="myorg/myproject"
# Conflict resolution strategy
GITHUB_SYNC_CONFLICT_STRATEGY="last_write_wins"
# Auto-sync on task modification (future feature)
GITHUB_SYNC_AUTO_SYNC="false"
# Fields to sync
GITHUB_SYNC_FIELDS="description,status,priority,tags,annotations"
# Tags to exclude from sync
GITHUB_SYNC_EXCLUDE_TAGS="private,local"
# Labels to exclude from sync
GITHUB_SYNC_EXCLUDE_LABELS="wontfix,duplicate"
# Annotation/comment prefixes
GITHUB_SYNC_TW_PREFIX="[TaskWarrior]"
GITHUB_SYNC_GH_PREFIX="[GitHub"
# Logging
GITHUB_SYNC_LOG_LEVEL="INFO"
GITHUB_SYNC_LOG_MAX_SIZE="10485760" # 10MB
GITHUB_SYNC_LOG_MAX_AGE="30" # days
# Rate limiting
GITHUB_SYNC_RATE_LIMIT_DELAY="1" # seconds between API calls
# Retry settings
GITHUB_SYNC_MAX_RETRIES="3"
GITHUB_SYNC_RETRY_DELAY="5" # seconds
# Debug mode
GITHUB_SYNC_DEBUG="false"# Open config file
$EDITOR $WORKWARRIOR_BASE/.config/github-sync/config.sh
# Or use direct path
vim ~/.workwarrior/profiles/my-profile/.config/github-sync/config.sh- Create task in TaskWarrior
- Create issue on GitHub
- Enable sync
- Work in TaskWarrior, push periodically
# Create task
task add "Fix login bug" priority:H +bug
# Create issue
ISSUE=$(gh issue create --repo myorg/myproject \
--title "Fix login bug" --body "Users can't log in" | grep -oP '#\K\d+')
# Enable sync
i enable-sync 1 $ISSUE myorg/myproject
# Work and push
task 1 start
task 1 annotate "Found root cause in auth.js"
i push 1
task 1 done
i push 1- Create issue on GitHub
- Create placeholder task
- Enable sync (pulls issue data)
- Work in TaskWarrior
# Issue already exists on GitHub (#123)
# Create placeholder task
task add "Placeholder" +feature
# Enable sync (will pull issue data)
i enable-sync 1 123 myorg/myproject
# Task now has issue title, labels, etc.
task 1 infoSync multiple tasks at once.
# Enable sync for multiple tasks
for task_id in 1 2 3 4 5; do
i enable-sync $task_id $((100 + task_id)) myorg/myproject
done
# Make changes to multiple tasks
task +feature modify priority:H
# Batch push
i push
# Or batch sync
i syncWhen both sides change, last-write-wins resolves conflicts.
# Modify task
task 1 modify "Updated locally"
# Someone modifies issue on GitHub
# (without syncing)
# Sync (will detect conflict and resolve)
i sync 1
# Check logs to see resolution
cat $WORKWARRIOR_BASE/.task/github-sync/errors.log | \
jq 'select(.type=="conflict_resolution")'Problem: GitHub CLI not installed.
Solution:
# macOS
brew install gh
# Linux
# See https://cli.github.com/Problem: GitHub CLI not authenticated.
Solution:
gh auth login
# Follow prompts to authenticateProblem: No write access to repository.
Solution:
- Verify repository access:
gh repo view myorg/myproject
- Check token scopes:
gh auth status
- Refresh token with correct scopes:
gh auth refresh -s repo
Problem: Invalid task ID or UUID.
Solution:
# List tasks
task list
# Use correct ID or UUID
i push 42
# or
i push abc123de-f456-7890-abcd-ef1234567890Problem: WORKWARRIOR_BASE not set.
Solution:
source bin/ww
p-my-profileProblem: Task description >256 characters.
Solution: This is expected. GitHub issue titles have a 256-character limit. The sync system automatically truncates with "..." at the end.
# Check truncated title
gh issue view 123 --repo myorg/myprojectProblem: Sync state database corrupted.
Solution:
# Reset sync state
rm $WORKWARRIOR_BASE/.task/github-sync/state.json
# Re-enable sync for affected tasks
i enable-sync 1 123 myorg/myprojectSync log (operation history):
cat $WORKWARRIOR_BASE/.task/github-sync/sync.logError log (errors and conflicts):
cat $WORKWARRIOR_BASE/.task/github-sync/errors.log | jq '.'Recent operations:
tail -20 $WORKWARRIOR_BASE/.task/github-sync/sync.logRecent errors:
tail -10 $WORKWARRIOR_BASE/.task/github-sync/errors.log | jq '.'A: No, each task can only be synced to one issue. This is a one-to-one relationship.
A: The task status changes to "deleted", which maps to GitHub state "CLOSED". The issue is closed but not deleted.
A: The issue state "CLOSED" maps to task status "completed". The task is marked as done.
A: Yes, as long as your GitHub token has access to the repository.
A: No, GitHub two-way sync works independently. However, it coexists with bugwarrior if you have it installed.
A: Use i disable-sync <task-id>. This preserves GitHub metadata but stops syncing.
A: Yes, each profile has independent sync state. However, be careful about conflicts if multiple profiles sync to the same issues.
A: System tags are TaskWarrior's internal tags like ACTIVE, READY, PENDING, COMPLETED, etc. These are automatically excluded from sync.
A: Use i sync-status to see all synced tasks and their GitHub issues.
A: Yes, edit the configuration file:
GITHUB_SYNC_TW_PREFIX="[TW]"
GITHUB_SYNC_GH_PREFIX="[GH"A: When both sides change, the system compares timestamps. The most recently modified side wins. If timestamps are equal, GitHub wins (tiebreaker).
A: No, sync operations are not reversible. However, you can manually revert changes in TaskWarrior or GitHub.
A: Disable sync, then re-enable with the new repository:
i disable-sync 1
i enable-sync 1 456 neworg/newrepoA: The system will detect rate limit errors and offer to wait. GitHub allows 5000 requests/hour for authenticated users.
- Read the Configuration Guide for advanced configuration
- See Integration Testing Guide for testing
- Check Troubleshooting Guide for common issues
- Review Architecture Documentation for developers