patchrelay 0.8.9 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +64 -62
  2. package/dist/agent-session-plan.js +17 -17
  3. package/dist/build-info.json +3 -3
  4. package/dist/cli/args.js +1 -1
  5. package/dist/cli/commands/issues.js +18 -18
  6. package/dist/cli/data.js +109 -298
  7. package/dist/cli/formatters/text.js +22 -28
  8. package/dist/cli/help.js +7 -7
  9. package/dist/cli/index.js +3 -3
  10. package/dist/config.js +13 -166
  11. package/dist/db/migrations.js +46 -154
  12. package/dist/db.js +369 -45
  13. package/dist/factory-state.js +55 -0
  14. package/dist/github-webhook-handler.js +199 -0
  15. package/dist/github-webhooks.js +166 -0
  16. package/dist/hook-runner.js +28 -0
  17. package/dist/http.js +48 -22
  18. package/dist/issue-query-service.js +33 -38
  19. package/dist/linear-workflow.js +5 -118
  20. package/dist/preflight.js +1 -6
  21. package/dist/project-resolution.js +12 -1
  22. package/dist/run-orchestrator.js +446 -0
  23. package/dist/{stage-reporting.js → run-reporting.js} +11 -13
  24. package/dist/service-runtime.js +12 -61
  25. package/dist/service-webhooks.js +7 -52
  26. package/dist/service.js +39 -61
  27. package/dist/webhook-handler.js +387 -0
  28. package/dist/webhook-installation-handler.js +3 -8
  29. package/package.json +2 -1
  30. package/dist/db/authoritative-ledger-store.js +0 -536
  31. package/dist/db/issue-projection-store.js +0 -54
  32. package/dist/db/issue-workflow-coordinator.js +0 -320
  33. package/dist/db/issue-workflow-store.js +0 -194
  34. package/dist/db/run-report-store.js +0 -33
  35. package/dist/db/stage-event-store.js +0 -33
  36. package/dist/db/webhook-event-store.js +0 -59
  37. package/dist/db-ports.js +0 -5
  38. package/dist/ledger-ports.js +0 -1
  39. package/dist/reconciliation-action-applier.js +0 -68
  40. package/dist/reconciliation-actions.js +0 -1
  41. package/dist/reconciliation-engine.js +0 -350
  42. package/dist/reconciliation-snapshot-builder.js +0 -135
  43. package/dist/reconciliation-types.js +0 -1
  44. package/dist/service-stage-finalizer.js +0 -753
  45. package/dist/service-stage-runner.js +0 -336
  46. package/dist/service-webhook-processor.js +0 -411
  47. package/dist/stage-agent-activity-publisher.js +0 -59
  48. package/dist/stage-event-ports.js +0 -1
  49. package/dist/stage-failure.js +0 -92
  50. package/dist/stage-handoff.js +0 -107
  51. package/dist/stage-launch.js +0 -84
  52. package/dist/stage-lifecycle-publisher.js +0 -284
  53. package/dist/stage-turn-input-dispatcher.js +0 -104
  54. package/dist/webhook-agent-session-handler.js +0 -228
  55. package/dist/webhook-comment-handler.js +0 -141
  56. package/dist/webhook-desired-stage-recorder.js +0 -122
  57. package/dist/webhook-event-ports.js +0 -1
  58. package/dist/workflow-policy.js +0 -149
  59. package/dist/workflow-ports.js +0 -1
  60. /package/dist/{installation-ports.js → github-types.js} +0 -0
package/README.md CHANGED
@@ -2,17 +2,18 @@
2
2
 
3
3
  PatchRelay is a self-hosted harness for Linear-driven Codex work on your own machine.
4
4
 
5
- It receives Linear webhooks, routes issues to the right local repository, prepares durable issue worktrees, runs staged Codex sessions through `codex app-server`, and keeps the whole run observable and resumable from the CLI.
5
+ It receives Linear webhooks, routes issues to the right local repository, prepares durable issue worktrees, runs Codex sessions through `codex app-server`, and keeps the whole run observable and resumable from the CLI. GitHub webhooks drive reactive loops for CI repair, review fixes, and merge queue failures.
6
6
 
7
7
  PatchRelay is the system around the model:
8
8
 
9
- - webhook intake and verification
9
+ - webhook intake and verification (Linear and GitHub)
10
10
  - Linear OAuth and workspace installations
11
11
  - issue-to-repo routing
12
12
  - issue worktree and branch lifecycle
13
- - stage orchestration and thread continuity
13
+ - run orchestration and thread continuity
14
+ - reactive CI repair, review fix, and merge queue repair loops
14
15
  - native Linear agent input forwarding into active runs
15
- - read-only inspection and stage reporting
16
+ - read-only inspection and run reporting
16
17
 
17
18
  If you want Codex to work inside your real repos with your real tools, secrets, SSH access, and deployment surface, PatchRelay is the harness that makes that loop reliable.
18
19
 
@@ -20,20 +21,22 @@ If you want Codex to work inside your real repos with your real tools, secrets,
20
21
 
21
22
  - Keep the agent in the real environment instead of rebuilding that environment in a hosted sandbox.
22
23
  - Use your existing machine, repos, secrets, SSH config, shell tools, and deployment access.
23
- - Keep deterministic workflow logic outside the model: routing, staging, worktree ownership, and reporting.
24
+ - Keep deterministic workflow logic outside the model: routing, run orchestration, worktree ownership, and reporting.
24
25
  - Choose the Codex approval and sandbox settings that match your risk tolerance.
25
- - Let Linear drive the loop through delegation, mentions, and workflow stages.
26
+ - Let Linear drive the loop through delegation and native agent sessions.
27
+ - Let GitHub drive reactive loops through PR reviews and CI check events.
26
28
  - Drop into the exact issue worktree and resume control manually when needed.
27
29
 
28
30
  ## What PatchRelay Owns
29
31
 
30
32
  PatchRelay does the deterministic harness work that you do not want to re-implement around every model run:
31
33
 
32
- - verifies and deduplicates Linear webhooks
33
- - maps issue events to the correct local project and workflow policy
34
+ - verifies and deduplicates Linear and GitHub webhooks
35
+ - maps issue events to the correct local project
34
36
  - creates and reuses one durable worktree and branch per issue lifecycle
35
- - starts or forks Codex threads for the workflows you bind to Linear states
36
- - persists enough state to correlate the Linear issue, local workspace, stage run, and Codex thread
37
+ - starts Codex threads for implementation runs
38
+ - triggers reactive runs for CI failures, review feedback, and merge queue failures
39
+ - persists enough state to correlate the Linear issue, local workspace, run, and Codex thread
37
40
  - reports progress back to Linear and forwards follow-up agent input into active runs
38
41
  - exposes CLI and optional read-only inspection surfaces so operators can understand what happened
39
42
 
@@ -41,10 +44,10 @@ PatchRelay does the deterministic harness work that you do not want to re-implem
41
44
 
42
45
  PatchRelay works best when read as five layers with clear ownership:
43
46
 
44
- - policy layer: repo workflow files and stage prompts
45
- - coordination layer: issue claiming, stage selection, retries, and reconciliation
47
+ - policy layer: repo workflow files (`IMPLEMENTATION_WORKFLOW.md`, `REVIEW_WORKFLOW.md`)
48
+ - coordination layer: issue claiming, run scheduling, retry budgets, and reconciliation
46
49
  - execution layer: durable worktrees, Codex threads, and queued turn input delivery
47
- - integration layer: Linear webhooks, OAuth, project routing, and deterministic state sync
50
+ - integration layer: Linear webhooks, GitHub webhooks, OAuth, project routing, and state sync
48
51
  - observability layer: CLI inspection, reports, event trails, and operator endpoints
49
52
 
50
53
  That separation is intentional. PatchRelay is not the policy itself and it is not the coding agent. It is the harness that keeps those pieces coordinated in a real repository with real operational state.
@@ -53,11 +56,11 @@ That separation is intentional. PatchRelay is not the policy itself and it is no
53
56
 
54
57
  PatchRelay is designed for a local, operator-owned setup:
55
58
 
56
- - PatchRelay service runs on your machine or server
59
+ - PatchRelay service runs on your machine or server (default `127.0.0.1:8787`)
57
60
  - Codex runs through `codex app-server`
58
61
  - Linear is the control surface
59
62
  - `patchrelay` CLI is the operator interface
60
- - a reverse proxy exposes the Linear-facing routes
63
+ - a reverse proxy exposes the Linear-facing and GitHub-facing webhook routes
61
64
 
62
65
  Linux and Node.js `24+` are the intended runtime.
63
66
 
@@ -67,53 +70,59 @@ You will also need:
67
70
  - `codex`
68
71
  - a Linear OAuth app for this PatchRelay deployment
69
72
  - a Linear webhook secret
70
- - a public HTTPS entrypoint such as Caddy, nginx, or a tunnel so Linear can reach your PatchRelay webhook
71
-
72
- For the exact OAuth app settings and webhook categories, use the Linear onboarding guide.
73
+ - a public HTTPS entrypoint such as Caddy, nginx, or a tunnel so Linear and GitHub can reach your PatchRelay webhooks
73
74
 
74
75
  ## How It Works
75
76
 
76
- 1. A human delegates PatchRelay on an issue to start automation, or mentions it to start a conversational agent session.
77
+ 1. A human delegates PatchRelay on an issue to start automation.
77
78
  2. PatchRelay verifies the webhook and routes the issue to the right local project.
78
- 3. Delegated issues create or reuse the issue worktree and launch the matching workflow through `codex app-server`.
79
+ 3. Delegated issues create or reuse the issue worktree and launch an implementation run through `codex app-server`.
79
80
  4. PatchRelay persists thread ids, run state, and observations so the work stays inspectable and resumable.
80
- 5. Mentions stay conversational, while delegated sessions and native agent prompts can steer the active run. An operator can take over from the exact same worktree when needed.
81
+ 5. GitHub webhooks drive reactive loops: CI repair on check failures, review fix on changes requested, and merge queue repair on queue failures.
82
+ 6. Native agent prompts and Linear comments can steer the active run. An operator can take over from the exact same worktree when needed.
83
+
84
+ ## Factory State Machine
85
+
86
+ Each issue progresses through a factory state machine:
87
+
88
+ ```text
89
+ delegated → preparing → implementing → pr_open → awaiting_review
90
+ → changes_requested (review fix run) → back to implementing
91
+ → repairing_ci (CI repair run) → back to pr_open
92
+ → awaiting_queue → done (merged)
93
+ → repairing_queue (queue repair run) → back to pr_open
94
+ → escalated or failed (when retry budgets are exhausted)
95
+ ```
96
+
97
+ Run types:
98
+
99
+ - `implementation` — initial coding work
100
+ - `review_fix` — address reviewer feedback
101
+ - `ci_repair` — fix failing CI checks
102
+ - `queue_repair` — fix merge queue failures
81
103
 
82
104
  ## Restart And Reconciliation
83
105
 
84
106
  PatchRelay treats restart safety as part of the harness contract, not as a best-effort extra.
85
107
 
86
- After a restart, the service should be able to answer:
108
+ After a restart, the service can answer:
87
109
 
88
110
  - which issue owns each active worktree
89
- - which stage was running or queued
111
+ - which run was active or queued
90
112
  - which Codex thread and turn belong to that work
91
113
  - whether the issue is still eligible to continue
92
114
  - whether the run should resume, hand off, or fail back to a human state
93
115
 
94
- This is why PatchRelay keeps a small harness ledger alongside Codex thread history and Linear state. The goal is not to duplicate the model transcript. The goal is to make automation restartable, inspectable, and recoverable when the process or machine is interrupted.
95
-
96
- ## Workflow Configuration
97
-
98
- PatchRelay keeps workflow configuration simple:
99
-
100
- - route issues to a project by team, issue prefix, or labels
101
- - when an issue is delegated to PatchRelay, it looks at the current Linear state
102
- - that Linear state selects the matching workflow to run
103
- - that workflow selects the repo-local workflow file
116
+ This is why PatchRelay keeps a durable `issues` and `runs` table alongside Codex thread history and Linear state. The goal is not to duplicate the model transcript. The goal is to make automation restartable, inspectable, and recoverable when the process or machine is interrupted.
104
117
 
105
- Most teams only configure:
118
+ ## Workflow Files
106
119
 
107
- - which issues belong to which project
108
- - which Linear states should wake each workflow
109
- - which workflow file belongs to each workflow
110
- - which active state PatchRelay should set while that workflow is running
120
+ PatchRelay uses repo-local workflow files as prompts for Codex runs:
111
121
 
112
- Examples:
122
+ - `IMPLEMENTATION_WORKFLOW.md` — used for implementation, CI repair, and queue repair runs
123
+ - `REVIEW_WORKFLOW.md` — used for review fix runs
113
124
 
114
- - a standard project can map `Start -> development`, `Review -> review`, and `Deploy -> deploy`
115
- - a push-to-main project can automate implementation and review, then let GitHub Actions handle deployment while PatchRelay moves failures back to `Human Needed`
116
- - a project with a QA gate can add a `qa` workflow bound to `Ready for QA`
125
+ These files define how the agent should work in that repository. Keep them short and action-oriented.
117
126
 
118
127
  ## Access Control
119
128
 
@@ -183,13 +192,11 @@ The generated `~/.config/patchrelay/patchrelay.json` is machine-level service co
183
192
 
184
193
  ### 5. Add workflow docs to the repo
185
194
 
186
- By default PatchRelay looks for:
195
+ PatchRelay looks for:
187
196
 
188
197
  ```text
189
198
  IMPLEMENTATION_WORKFLOW.md
190
199
  REVIEW_WORKFLOW.md
191
- DEPLOY_WORKFLOW.md
192
- CLEANUP_WORKFLOW.md
193
200
  ```
194
201
 
195
202
  These files define how the agent should work in that repo.
@@ -214,14 +221,14 @@ Important:
214
221
 
215
222
  - Linear needs a public HTTPS URL to reach your webhook.
216
223
  - `patchrelay init <public-base-url>` writes `server.public_base_url`, which PatchRelay uses when it prints webhook URLs.
217
- - For ingress, OAuth app setup, and webhook details, use the self-hosting and Linear onboarding docs.
224
+ - For ingress, OAuth app setup, and webhook details, use the self-hosting docs.
218
225
 
219
226
  ## Daily Loop
220
227
 
221
228
  1. Delegate a Linear issue to the PatchRelay app.
222
- 2. PatchRelay reads the current Linear state like `Start`, `Ready for QA`, or `Deploy` to choose the matching workflow.
223
- 3. Linear sends the delegation and agent-session webhooks to PatchRelay, which creates or reuses the issue worktree and launches the matching workflow.
224
- 4. Follow up in the Linear agent session to steer the active run or wake it with fresh input while it remains delegated.
229
+ 2. Linear sends the delegation and agent-session webhooks to PatchRelay, which creates or reuses the issue worktree and launches an implementation run.
230
+ 3. Follow up in the Linear agent session to steer the active run or wake it with fresh input while it remains delegated.
231
+ 4. GitHub webhooks automatically trigger CI repair, review fix, or merge queue repair runs when needed.
225
232
  5. Watch progress from the terminal or open the same worktree and take over manually.
226
233
 
227
234
  Useful commands:
@@ -233,7 +240,7 @@ Useful commands:
233
240
  - `patchrelay events APP-123 --follow`
234
241
  - `patchrelay worktree APP-123 --cd`
235
242
  - `patchrelay open APP-123`
236
- - `patchrelay retry APP-123 --stage review`
243
+ - `patchrelay retry APP-123`
237
244
 
238
245
  `patchrelay open` is the handoff bridge: it opens Codex in the issue worktree and resumes the existing thread when PatchRelay has one.
239
246
 
@@ -244,27 +251,22 @@ Today that takeover path is intentionally YOLO mode: it launches Codex with `--d
244
251
  PatchRelay keeps enough durable state to answer the questions that matter during and after a run:
245
252
 
246
253
  - which worktree and branch belong to an issue
247
- - which stage is active or queued
254
+ - which run is active or queued
248
255
  - which Codex thread owns the current work
249
256
  - what the agent said
250
257
  - which commands it ran
251
258
  - which files it changed
252
- - whether the stage completed, failed, or needs handoff
259
+ - whether the run completed, failed, or needs handoff
253
260
 
254
261
  ## Docs
255
262
 
256
263
  Use the README for the product overview and quick start. Use the docs for operating details:
257
264
 
258
- - [Self-hosting and deployment](https://github.com/krasnoperov/patchrelay/blob/main/docs/self-hosting.md)
259
- - [Linear agent onboarding](https://github.com/krasnoperov/patchrelay/blob/main/docs/linear-agent-onboarding.md)
260
- - [CLI reference](https://github.com/krasnoperov/patchrelay/blob/main/docs/cli-reference.md)
261
- - [Architecture](https://github.com/krasnoperov/patchrelay/blob/main/docs/architecture.md)
262
- - [Module map](https://github.com/krasnoperov/patchrelay/blob/main/docs/module-map.md)
263
- - [Authoritative vs derived state](https://github.com/krasnoperov/patchrelay/blob/main/docs/state-authority.md)
264
- - [Persistence audit](https://github.com/krasnoperov/patchrelay/blob/main/docs/persistence-audit.md)
265
- - [Codex integration details](https://github.com/krasnoperov/patchrelay/blob/main/docs/codex-workflow.md)
266
- - [Workflow file requirements](https://github.com/krasnoperov/patchrelay/blob/main/docs/IMPLEMENTATION_WORKFLOW_REQUIREMENTS.md)
267
- - [Security policy](https://github.com/krasnoperov/patchrelay/blob/main/SECURITY.md)
265
+ - [Self-hosting and deployment](./docs/self-hosting.md)
266
+ - [Architecture](./docs/architecture.md)
267
+ - [Design principles](./docs/design-docs/core-beliefs.md)
268
+ - [External reference patterns](./docs/references/external-patterns.md)
269
+ - [Security policy](./SECURITY.md)
268
270
 
269
271
  ## Status
270
272
 
@@ -1,5 +1,5 @@
1
- function formatStageLabel(stage) {
2
- return stage.replace(/[-_]+/g, " ");
1
+ function formatRunLabel(runType) {
2
+ return runType.replace(/[-_]+/g, " ");
3
3
  }
4
4
  function titleCase(value) {
5
5
  return value
@@ -8,27 +8,27 @@ function titleCase(value) {
8
8
  .map((word) => word[0].toUpperCase() + word.slice(1))
9
9
  .join(" ");
10
10
  }
11
- function buildPlan(stage, statuses) {
12
- const stageLabel = titleCase(formatStageLabel(stage));
11
+ function buildPlan(runType, statuses) {
12
+ const label = titleCase(formatRunLabel(runType));
13
13
  return [
14
14
  { content: "Prepare workspace", status: statuses[0] },
15
- { content: `Run ${stageLabel} workflow`, status: statuses[1] },
16
- { content: "Review next Linear step", status: statuses[2] },
15
+ { content: `Run ${label}`, status: statuses[1] },
16
+ { content: "Review outcome", status: statuses[2] },
17
17
  ];
18
18
  }
19
- export function buildPreparingSessionPlan(stage) {
20
- return buildPlan(stage, ["inProgress", "pending", "pending"]);
19
+ export function buildPreparingSessionPlan(runType) {
20
+ return buildPlan(runType, ["inProgress", "pending", "pending"]);
21
21
  }
22
- export function buildRunningSessionPlan(stage) {
23
- return buildPlan(stage, ["completed", "inProgress", "pending"]);
22
+ export function buildRunningSessionPlan(runType) {
23
+ return buildPlan(runType, ["completed", "inProgress", "pending"]);
24
24
  }
25
- export function buildCompletedSessionPlan(stage) {
26
- return buildPlan(stage, ["completed", "completed", "completed"]);
25
+ export function buildCompletedSessionPlan(runType) {
26
+ return buildPlan(runType, ["completed", "completed", "completed"]);
27
27
  }
28
- export function buildAwaitingHandoffSessionPlan(stage) {
29
- return buildPlan(stage, ["completed", "completed", "inProgress"]);
28
+ export function buildAwaitingHandoffSessionPlan(runType) {
29
+ return buildPlan(runType, ["completed", "completed", "inProgress"]);
30
30
  }
31
- export function buildFailedSessionPlan(stage, stageRun) {
32
- const workflowStepStatus = stageRun?.threadId || stageRun?.turnId ? "completed" : "inProgress";
33
- return buildPlan(stage, ["completed", workflowStepStatus, "pending"]);
31
+ export function buildFailedSessionPlan(runType, run) {
32
+ const workflowStepStatus = run?.threadId || run?.turnId ? "completed" : "inProgress";
33
+ return buildPlan(runType, ["completed", workflowStepStatus, "pending"]);
34
34
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.8.9",
4
- "commit": "a82762db78db",
5
- "builtAt": "2026-03-19T12:29:25.534Z"
3
+ "version": "0.9.1",
4
+ "commit": "cb0e47c488a3",
5
+ "builtAt": "2026-03-22T18:27:36.425Z"
6
6
  }
package/dist/cli/args.js CHANGED
@@ -69,7 +69,7 @@ export function resolveCommand(parsed) {
69
69
  export function hasHelpFlag(parsed) {
70
70
  return parsed.flags.get("help") === true;
71
71
  }
72
- export function getStageFlag(value) {
72
+ export function getRunTypeFlag(value) {
73
73
  if (typeof value !== "string") {
74
74
  return undefined;
75
75
  }
@@ -1,5 +1,5 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
- import { getStageFlag, parsePositiveIntegerFlag } from "../args.js";
2
+ import { getRunTypeFlag, parsePositiveIntegerFlag } from "../args.js";
3
3
  import { formatJson } from "../formatters/json.js";
4
4
  import { formatEvents, formatInspect, formatList, formatLive, formatOpen, formatReport, formatRetry, formatWorktree } from "../formatters/text.js";
5
5
  import { buildOpenCommand } from "../interactive.js";
@@ -28,7 +28,7 @@ export async function handleLiveCommand(params) {
28
28
  throw new Error(`No active stage found for ${issueKey}`);
29
29
  }
30
30
  writeOutput(params.stdout, params.json ? formatJson(result) : formatLive(result));
31
- if (!watch || result.stageRun.status !== "running") {
31
+ if (!watch || result.run.status !== "running") {
32
32
  break;
33
33
  }
34
34
  await delay(2000);
@@ -41,13 +41,13 @@ export async function handleReportCommand(params) {
41
41
  throw new Error("report requires <issueKey>.");
42
42
  }
43
43
  const reportOptions = {};
44
- const stage = getStageFlag(params.parsed.flags.get("stage"));
45
- if (stage) {
46
- reportOptions.stage = stage;
44
+ const runType = getRunTypeFlag(params.parsed.flags.get("run-type"));
45
+ if (runType) {
46
+ reportOptions.runType = runType;
47
47
  }
48
- const stageRunId = parsePositiveIntegerFlag(params.parsed.flags.get("stage-run"), "--stage-run");
49
- if (stageRunId !== undefined) {
50
- reportOptions.stageRunId = stageRunId;
48
+ const runId = parsePositiveIntegerFlag(params.parsed.flags.get("run"), "--run");
49
+ if (runId !== undefined) {
50
+ reportOptions.runId = runId;
51
51
  }
52
52
  const result = params.data.report(issueKey, reportOptions);
53
53
  if (!result) {
@@ -63,22 +63,22 @@ export async function handleEventsCommand(params) {
63
63
  }
64
64
  const follow = params.parsed.flags.get("follow") === true;
65
65
  let afterId;
66
- let stageRunId = parsePositiveIntegerFlag(params.parsed.flags.get("stage-run"), "--stage-run");
66
+ let runId = parsePositiveIntegerFlag(params.parsed.flags.get("run"), "--run");
67
67
  do {
68
68
  const result = params.data.events(issueKey, {
69
- ...(stageRunId !== undefined ? { stageRunId } : {}),
69
+ ...(runId !== undefined ? { runId } : {}),
70
70
  ...(typeof params.parsed.flags.get("method") === "string" ? { method: String(params.parsed.flags.get("method")) } : {}),
71
71
  ...(afterId !== undefined ? { afterId } : {}),
72
72
  });
73
73
  if (!result) {
74
- throw new Error(`Stage run not found for ${issueKey}`);
74
+ throw new Error(`Run not found for ${issueKey}`);
75
75
  }
76
- stageRunId = result.stageRun.id;
76
+ runId = result.run.id;
77
77
  if (result.events.length > 0) {
78
78
  writeOutput(params.stdout, params.json ? formatJson(result) : formatEvents(result));
79
79
  afterId = result.events.at(-1)?.id;
80
80
  }
81
- if (!follow || result.stageRun.status !== "running") {
81
+ if (!follow || result.run.status !== "running") {
82
82
  break;
83
83
  }
84
84
  await delay(2000);
@@ -115,7 +115,7 @@ export async function handleOpenCommand(params) {
115
115
  if (!result) {
116
116
  throw new Error(`Workspace not found for ${issueKey}`);
117
117
  }
118
- const openCommand = buildOpenCommand(params.config, result.workspace.worktreePath, result.resumeThreadId);
118
+ const openCommand = buildOpenCommand(params.config, result.worktreePath, result.resumeThreadId);
119
119
  writeOutput(params.stdout, formatOpen(result, openCommand));
120
120
  return 0;
121
121
  }
@@ -123,7 +123,7 @@ export async function handleOpenCommand(params) {
123
123
  if (!result) {
124
124
  throw new Error(`Workspace not found for ${issueKey}`);
125
125
  }
126
- const openCommand = buildOpenCommand(params.config, result.workspace.worktreePath, result.resumeThreadId);
126
+ const openCommand = buildOpenCommand(params.config, result.worktreePath, result.resumeThreadId);
127
127
  return await params.runInteractive(openCommand.command, openCommand.args);
128
128
  }
129
129
  export async function handleRetryCommand(params) {
@@ -132,9 +132,9 @@ export async function handleRetryCommand(params) {
132
132
  throw new Error("retry requires <issueKey>.");
133
133
  }
134
134
  const retryOptions = {};
135
- const stage = getStageFlag(params.parsed.flags.get("stage"));
136
- if (stage) {
137
- retryOptions.stage = stage;
135
+ const runType = getRunTypeFlag(params.parsed.flags.get("run-type"));
136
+ if (runType) {
137
+ retryOptions.runType = runType;
138
138
  }
139
139
  if (typeof params.parsed.flags.get("reason") === "string") {
140
140
  retryOptions.reason = String(params.parsed.flags.get("reason"));