Skip to content

feat: cross-runner handoff (/handoff claude|codex) for Linear issues#1348

Open
hcaumo wants to merge 15 commits into
cyrusagents:mainfrom
hcaumo:volcano-voice
Open

feat: cross-runner handoff (/handoff claude|codex) for Linear issues#1348
hcaumo wants to merge 15 commits into
cyrusagents:mainfrom
hcaumo:volcano-voice

Conversation

@hcaumo

@hcaumo hcaumo commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Adds cross-runner handoff to Cyrus: a Linear issue started with one runner (Claude or Codex) can be handed to the other so it continues from the existing work, files, branch, and PR state — in the same worktree, never running both runners at once.

@Cyrus /handoff codex     # hand the work to Codex
@Cyrus /handoff claude    # hand the work to Claude

Initial-runner tags ([agent=claude] / [agent=codex]) are unchanged.

How it works

  • HandoffService (packages/edge-worker/src/HandoffService.ts) — command parsing (parseHandoffCommand), worktree/git/PR snapshot (buildSnapshot), target-prompt formatting (buildHandoffPrompt), a stop-poller (waitForStopped), and getActiveRunnerType.
  • runnerTypeOverride threaded through RunnerConfigBuilder.buildIssueConfigEdgeWorker.buildAgentRunnerConfigresumeAgentSession. When set, it forces the target runner, bypassing label/description selection and the sticky-session binding. Defaults to undefined, so existing single-runner behavior is byte-for-byte unchanged.
  • Follow-up path (handleHandoffCommand): stops the active runner (sequentially, with a ~30s timeout → "handoff blocked" comment), snapshots the worktree state, clears the old runner binding, and starts the target in the same session.workspace.
  • Creation path: Linear @mentions create a new agent session, so @Cyrus /handoff codex arrives on the session-creation webhook. The creating comment is parsed and the requested runner is forced for the new session (the issue's worktree is reused, so the target picks up prior files/commits/PR).
  • Best-effort git/PR snapshot reads added to GitService; getLastAssistantBody / clearRunnerSessionBindings added to AgentSessionManager.

Testing

  • New unit/integration tests: command parsing, runner-type detection, snapshot building, prompt formatting, stop-poller, runnerTypeOverride, same-worktree reuse, blocked handoff, failure handling, and creation-path handoff selection. Full cyrus-edge-worker suite: 765 passing; pnpm typecheck clean.
  • Validated live against Linear on a self-hosted Cyrus: @Cyrus /handoff codex on a Claude-started issue stopped Claude and started a Codex runner in the same worktree, continuing the work.

Acceptance criteria

  • [agent=claude] / [agent=codex] still start exactly one runner (regression-guarded).
  • /handoff codex after Claude starts Codex in the same worktree; /handoff claude after Codex starts Claude.
  • Never runs both runners concurrently in the same worktree.
  • Both runners report to the same Linear issue.
  • Tests cover parsing, runner selection, same-worktree reuse, blocked handoff, snapshot creation, and failure handling.

Changelog updated under ## [Unreleased].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant