A CLI that bridges Linear and GitHub Issues with worktrunk (wt) worktree management,
exposed as two Claude Code slash commands: /push-issue and /pull-issue.
The problem: mid-session you spot a tangential bug. If you fix it now you muddy your context. If you don't write it down you forget it. CTW captures it as a ticket in two keystrokes and queues it in an isolated worktree for later — without breaking your flow.
- uv — Python package manager
- worktrunk (
wt) — git worktree manager - Claude Code with slash command support
- Linear Personal API Key and/or GitHub personal access token or
ghCLI
GitHub token scopes required:
repo— for private repos (full access)read:user— to fetch assigned issuespublic_repo— sufficient for public repos only
git clone https://github.com/quickvm/ctw
cd ctw
uv tool install -e .
ctw --helpCTW resolves the active tracker through a five-step precedence chain (highest wins):
--tracker <profile>flag on any commandCTW_DEFAULT_TRACKERenvironment variabledefault_trackerkey in~/.config/ctw/config.tomlCTW_PROVIDERin.envin the current working directory- First profile defined in
~/.config/ctw/config.toml
ctw initWalks through provider selection, auth, and writes ~/.config/ctw/config.toml. Also offers
to symlink slash commands and configure wt for the current repo.
Create ~/.config/ctw/config.toml:
default_tracker = "work"
[work]
provider = "linear"
linear_api_key = "lin_api_work_xxx"
linear_team_id = "team_id_here"
[personal]
provider = "github"
github_auth = "gh-cli" # use gh CLI — token stays fresh as gh rotates it
github_repo = "jdoss/personal" # default repo for bare issue numbers (e.g. "42")
[quickvm]
provider = "github"
github_token = "ghp_xxx" # manual PAT
github_repo = "jdoss/quickvm"github_auth values:
"token"(default) — readsgithub_tokenfrom config orCTW_GITHUB_TOKENenv var"gh-cli"— callsgh auth tokenat runtime; no token stored on disk
export CTW_PROVIDER=linear
export CTW_LINEAR_API_KEY=lin_api_xxx
export CTW_LINEAR_TEAM_ID=team_id_hereCopy .env.example → .env in any project directory for per-project overrides.
ctw config-showShows all resolved values with masked credentials and marks unset fields.
ctw set-default workUpdates default_tracker in ~/.config/ctw/config.toml. Comments and formatting in the file are preserved.
ctw install-commandsCreates symlinks in ~/.claude/commands/ pointing to the installed package files. Re-run
after uv tool install upgrades to refresh symlinks to the new location.
mkdir -p ~/.claude/commands
ln -sf "$(python -c 'import ctw; print(__import__("pathlib").Path(ctw.__file__).parent)')/commands/push-issue.md" \
~/.claude/commands/push-issue.md
ln -sf "$(python -c 'import ctw; print(__import__("pathlib").Path(ctw.__file__).parent)')/commands/pull-issue.md" \
~/.claude/commands/pull-issue.mdctw configure-wtReads .config/wt.toml in the current repo (creating it if absent), merges in a
[post-create] hook, and writes back — preserving all existing comments and hooks.
Commit the result so everyone on the team gets the hook automatically.
git add .config/wt.toml && git commit -m "Add CTW worktree hook"The hook runs when wt switch --create <branch> creates a new worktree. It detects
ticket identifiers in the branch name and fetches TASK.md automatically:
- Linear branches: matches
ENG-123pattern - GitHub branches: matches trailing number, requires
CTW_GITHUB_REPOor--tracker
# Embed a specific tracker profile in the hook script
ctw configure-wt --tracker work
# Overwrite an existing hook
ctw configure-wt --force
# Target a different config file
ctw configure-wt --config path/to/wt.tomlSafe to re-run — refuses to overwrite an existing hook without --force.
Copy the snippet from config/wt.toml.example into your project's .config/wt.toml.
You're deep in a refactor. You spot an auth middleware bug.
# In your current Claude Code session:
/push-issue the logout handler throws a 500 when session is None — stack trace in auth/middleware.py:142 --tracker work
# → "Create a background worktree? [y/N]" → y
# ✓ ENG-456 "Fix auth middleware null pointer" [work] → branch eng-456-fix-auth-middleware (worktree created)
# Resume what you were doing.
Later, in a fresh terminal:
/pull-issue ENG-456 --tracker work
# Claude fetches the ticket, switches to the worktree, reads TASK.md, briefs you, and starts.
/push-issue logout button sends 500 when cookie is expired --tracker quickvm
# ✓ jdoss/quickvm#42 "Fix logout 500 on expired cookie" [quickvm] → branch jdoss-quickvm-42-fix-logout-500
# Later:
/pull-issue jdoss/quickvm#42 --tracker quickvm
If configure-wt was run in a repo, the hook fires automatically:
wt switch --create eng-456-fix-auth-middleware
# → post-create hook runs
# → Loaded ENG-456 → TASK.mdThe worktree has TASK.md waiting before you even open your editor.
| Command | Description |
|---|---|
ctw init |
Interactive setup wizard |
ctw list-issues [--tracker] |
List issues assigned to me |
ctw get-issue <id> [--tracker] |
Show full issue details |
ctw create-issue <title> [desc] [--tracker] [--team] [--priority] |
Create issue |
ctw list-teams [--tracker] |
List teams or repos |
ctw context <id> [--tracker] [-o FILE] |
Render TASK.md context block |
ctw slug <id> [--tracker] |
Print git-safe branch name (no newline) |
ctw set-default <tracker> |
Set default tracker in config.toml |
ctw config-show [--tracker] |
Show resolved config with masked credentials |
ctw install-commands |
Symlink slash commands to ~/.claude/commands/ |
ctw configure-wt [--tracker] [--force] [--config PATH] |
Add post-create hook to .config/wt.toml |
All commands that interact with a provider accept --tracker / -k <profile> to override the
active tracker for that invocation.
slug prints with no trailing newline, designed for use in shell substitution:
git checkout -b $(ctw slug ENG-123 --tracker work)
wt switch --create $(ctw slug jdoss/quickvm#42 --tracker quickvm)Branch name format:
- Linear:
eng-123-fix-null-check-in-auth-middleware(title truncated at 40 chars) - GitHub:
jdoss-quickvm-42-fix-null-check(/and#replaced with-)
MIT © 2026 QuickVM, LLC