Live context
!git rev-parse --show-toplevel 2>/dev/null && bash skills/dev-workflow/_shared/scripts/detect-platform.sh 2>/dev/null | sed 's/^/platform: /' || echo "not a git repo"
!ls -1 .dev-files/objectives 2>/dev/null | sed 's/^/objective: /' || true
!gh issue list --state open --label agent:any --limit 3 --json number,title --jq '.[] | " #\(.number): \(.title)"' 2>/dev/null || true
switch-dev
Hand off and resume development work across Claude Code, Cursor, Codex, Gemini CLI and OpenCode.
Why this skill exists
Rate limits and tool preferences force mid-task switches, and session context does not transfer between tools. The earlier version solved this with GitHub issues as the shared memory — which works, but makes a network service and a gh login mandatory for a purely local operation, and quietly assumed only three platforms exist.
State now lives locally first: .dev-files/objectives/<id>/ holds the objective, its tasks, and a structured handoff record per task, conforming to core/workflow/{objective,task,handoff}-schema.json. Git holds the code. The GitHub issue Resume block is still fully supported as optional remote persistence — for work that other people need to see, or a switch to a different machine.
Validation tier
Tier 0. This skill moves state; it runs no tests and makes no code commits (it will push existing commits so the next agent can see them).
Supporting files
| Path | When to read |
|---|---|
references/mode-contracts.md |
Mode semantics, argument shapes, shared script table |
references/resume-schema.md |
Handoff record fields and the Resume block mirror |
references/platform-capabilities.md |
All five platforms; how to degrade a handoff for the target |
scripts/parse-mode-args.sh |
Deterministic mode/issue/objective/task/platform parsing |
templates/resume-block.md.tmpl |
Canonical Resume markdown |
templates/handoff-comment.md.tmpl |
Issue comment shape |
../_shared/scripts/* |
Platform detection, worktrees, objective state, handoff I/O, issue Resume I/O |
Resolve paths and platform
SKILL_DIR="${CLAUDE_SKILL_DIR:-$(git rev-parse --show-toplevel)/skills/dev-workflow/switch-dev}"
SHARED_DIR="$(cd "$SKILL_DIR/../_shared/scripts" && pwd)"
REPO_ROOT="$(git rev-parse --show-toplevel)"
PLATFORM="$(bash "$SHARED_DIR/detect-platform.sh")"
detect-platform.sh returns one of claude, cursor, codex, gemini, opencode. If it returns a value you do not recognise, treat the platform as unknown, ask the user which tool they are on rather than guessing, and continue — an unrecognised platform must never abort a handoff.
Parse input
PARSED="$(bash "$SKILL_DIR/scripts/parse-mode-args.sh" $ARGUMENTS)"
MODE="$(echo "$PARSED" | jq -r .mode)"
ISSUE="$(echo "$PARSED" | jq -r '.issue // empty')"
OBJECTIVE="$(echo "$PARSED" | jq -r '.objective // empty')"
TASK="$(echo "$PARSED" | jq -r '.task // empty')"
TARGET="$(echo "$PARSED" | jq -r '.target_platform // "any"')"
| Invocation | Result |
|---|---|
/switch-dev handoff #42 |
handoff, issue 42 |
/switch-dev handoff #42 cursor |
handoff → agent:cursor |
/switch-dev handoff objective=auth-system task-002 gemini |
handoff of one task → agent:gemini |
/switch-dev resume #42 |
resume |
/switch-dev resume objective=auth-system |
resume the whole objective |
/switch-dev status |
status |
/switch-dev #42 |
handoff (default mode) |
When neither an issue nor an objective is given, fall back to the single active objective under .dev-files/objectives/; if there is more than one, ask which.
Mode: handoff
Step 1 — Preconditions
- Identify the unit being handed off: objective + task, or issue number, or just the current branch.
- Confirm branch and worktree (
git branch --show-current, cwd). - Push commits — the next agent can only see pushed work.
git status -sb
git push -u origin HEAD 2>/dev/null || echo "WARN: unpushed commits remain — the next agent will not see them"
Step 2 — Write the local handoff record (primary)
bash "$SHARED_DIR/handoff.sh" emit "$OBJECTIVE" "$TASK" \
--status partial \
--summary "..." \
--branch "$BRANCH" \
--commit "$SHA" \
--file 'src/auth/middleware.ts:modified' \
--validation 'npm test -- src/auth|worker|pass' \
--decision 'used jose over jsonwebtoken|maintained, ESM-native' \
--followup 'refresh-token rotation still unimplemented|blocking'
handoff.sh emit writes and validates the record against core/workflow/handoff-schema.json; handoff.sh show <objective> <task> reads one back and handoff.sh list <objective> lists them. The fields that carry the context a session loses:
| Field | Fill with |
|---|---|
status |
completed, partial, failed, or blocked |
summary |
One paragraph: what was done, what the next agent must know first |
branch, commits |
Real branch and real SHAs — never fabricated |
files_changed |
Every path touched |
validation |
Only commands actually run, with their result; skipped needs a skip_reason |
decisions |
Choices made and why — this is the part that is otherwise lost |
risks, followups |
What the next agent must not rediscover the hard way |
If handoff.sh is unavailable, write the JSON to .dev-files/objectives/<id>/handoffs/<task-id>.json directly — the path and the schema are the contract, the helper is a convenience.
No objective? A plain branch handoff is still valid: write the same record under .dev-files/objectives/adhoc-<branch-slug>/handoffs/task-001.json, or, when the user wants nothing on disk, put the same fields in the issue Resume block and say that local state was skipped.
Step 3 — Mirror to GitHub (optional)
Only when an issue number is in play and gh is available:
FIELDS_FILE="$(mktemp)"
jq -nc \
--arg done "..." --arg next "..." --arg decisions "..." --arg blocked "none" \
--arg branch "$BRANCH" --arg worktree "$WORKTREE" \
--arg objective "$OBJECTIVE" --arg task "$TASK" \
--arg last_agent "$PLATFORM" \
'{done: $done, next: $next, decisions: $decisions, blocked: $blocked, branch: $branch, worktree: $worktree, objective: $objective, task: $task, last_agent: $last_agent}' \
> "$FIELDS_FILE"
TARGET_LABEL="agent:${TARGET:-any}"
bash "$SHARED_DIR/update-issue-resume.sh" "$ISSUE" "$FIELDS_FILE" "$TARGET_LABEL"
rm -f "$FIELDS_FILE"
Merge with the existing Resume via parse-issue-resume.sh — never erase prior Done items; append.
If gh is missing, the repo has no remote, or the push failed: say so in one line and continue. A missing mirror is not a failed handoff.
Step 4 — Check the target's capabilities
Read the target platform's row in core/capabilities/platforms.json (summary in references/platform-capabilities.md) for anything the remaining work assumes. Name any degradation explicitly in the handoff note — e.g. "target has no parallel_subagents: run waves 2 and 3 sequentially".
Step 5 — Print the resume prompt
Read .dev-files/objectives/auth-system/handoffs/task-002.json (and issue #42 Resume, if you have gh).
Branch: `worker/auth-system/002`. Worktree: `.agent-worktrees/auth-system/task-002`.
Continue the **Next** item. Do not redo anything under **Done**.
Degraded here: no parallel subagents — run remaining tasks sequentially.
Start with: /switch-dev resume objective=auth-system task-002
Suggest references/platform-capabilities.md when the user might be better off staying on the current tool.
Mode: resume
Step 1 — Load state, local first
bash "$SHARED_DIR/objective-state.sh" show "$OBJECTIVE"
bash "$SHARED_DIR/objective-state.sh" tasks "$OBJECTIVE"
bash "$SHARED_DIR/handoff.sh" show "$OBJECTIVE" "$TASK"
# Optional mirror, when an issue is known and gh is available:
bash "$SHARED_DIR/parse-issue-resume.sh" "$ISSUE"
If the two disagree, local state and git win — the mirror is a copy and may be stale. Say which one you used.
If blocked / status: blocked is set, stop and report the blocker before doing anything else.
Step 2 — Resolve the worktree
bash "$SHARED_DIR/resolve-worktree.sh" "$REPO_ROOT" "$BRANCH"
Use the returned worktree_path for all edits. If the recorded worktree is a legacy .<platform>/.worktrees/… path from another tool, note it and use the current resolution for the same branch — the branch is the identity, the path is not.
Step 3 — Claim it
gh issue edit "$ISSUE" --remove-label "agent:any" 2>/dev/null || true
gh issue edit "$ISSUE" --add-label "agent:${PLATFORM}"
Skip silently when there is no issue or no gh. Locally, record the claim by setting the task's provider to the current platform — metadata only, never part of a branch or path.
Step 4 — Continuation checklist
Objective: auth-system — Implement authentication system
Task: task-002 (implementer) — status partial
Done: ...
Next: ...
Branch / worktree: ...
Decisions: ...
Risks / followups: ...
Source: local handoff record (issue #42 mirror was 2 commits stale)
Continue with **Next**. Run /start-dev to pick the task up (no PR — the objective owns delivery).
Mode: status
Read-only dashboard. Local first, GitHub if available.
# Local objectives and task states
for d in .dev-files/objectives/*/; do
[ -f "$d/objective.json" ] || continue
jq -r '"\(.id) [\(.status)] \(.title)"' "$d/objective.json"
jq -r '" \(.id) \(.role) \(.status)"' "$d"/tasks/*.json 2>/dev/null
done
# Optional: agent-labelled issues
gh issue list --state open --json number,title,labels --jq '
.[] | select([.labels[].name] | any(startswith("agent:"))) |
"#\(.number) \(.title) [\([.labels[].name | select(startswith("agent:"))] | join(", "))]"
' 2>/dev/null || echo "(gh unavailable — local state only)"
bash "$SHARED_DIR/list-agent-worktrees.sh" "$REPO_ROOT"
Print:
| Objective / Issue | Task | Branch | Owner | Worktree | Next |
|---|
Hard rules
- Never hand off without writing the handoff record. A verbal summary in a chat window is not state.
- Never fabricate commits or validation results in a handoff. Only what actually ran.
- Never skip the push warning when commits are unpushed — invisible work is the most common handoff failure.
- Never treat GitHub as required.
ghmissing degrades the mirror, never the handoff. - Never let the mirror override local state and git when they disagree.
- Never encode the provider in a branch name or worktree path. Provider is metadata.
- Never merge PRs — that is
/pr-dev. - Never use third-party handoff MCP servers — local files, git, and (optionally) GitHub only.
- Never assume the target platform's capabilities. Check the registry and state the degradation.
Pipeline integration
| From | To |
|---|---|
plan-dev |
Writes the objective + tasks (issues optional) → /switch-dev status shows them |
orchestrate-dev |
Emits per-task handoffs → /switch-dev resume objective=<id> task-NNN |
worker-dev / start-dev |
On pause or rate limit → /switch-dev handoff |
switch-dev resume |
→ continue in the worktree, or /start-dev |
pr-dev |
When blocked on a human → /switch-dev handoff #N |
Scope boundary
This skill moves task/session state between tools. It does NOT configure MCP servers, install external orchestrators, run validation, or modify hook worktree paths under ~/.claude/worktrees/.