fix(orchestration): refuse --retry-of beside --spec at the schema

--spec creates a new Task, so a retry link to a prior Dispatch could
never resolve; the refusal named a Task id the caller never supplied.
Refuse up front with the flag the caller needs, and say so in the help
note and the recovery reference.
This commit is contained in:
Jinwoo-H
2026-09-06 13:53:52 -04:00
parent 06a609c487
commit 2da076d4e9
5 changed files with 37 additions and 5 deletions
@@ -109,8 +109,9 @@ optional.
## Retry, stop, and abandon
Retry only a positively proven failed or stopped attempt. Placement is never
silently inherited:
Retry only a positively proven failed or stopped attempt. Name the failed Task
with `--task`, since `--spec` creates a new one. Placement is never silently
inherited:
```text
ORCA orchestration worker-start --task <task_id> --retry-of <dispatch_id> --worktree <explicit_placement> --agent <agent> --json
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -39,7 +39,7 @@ export const ORCHESTRATION_WORKER_COMMAND_SPECS: CommandSpec[] = [
'Creation flags (--name, --repo, --base-branch, --display-name, --comment, --setup) are rejected for current/existing worktrees. Use exact --repo on the selected server; project/host convenience routing remains on worktree create.',
'--on selects only the worker server; the Run and this command remain on the current Orca server.',
'Remote current and new-child are invalid; discover an exact remote selector or use new-top-level.',
'--retry-of links the replacement attempt but does not inherit placement; repeat the intended --on/worktree and --agent/terminal choices.',
'--retry-of needs --task naming the failed Task (--spec creates a new one) and does not inherit placement; repeat the intended --on/worktree and --agent/terminal choices.',
'The call exits 0 only for ready. Failed or outcome_unknown exits 1 and JSON includes stage/failedStage, setup, effects, residualResources, and recovery commands when needed.'
]
},
@@ -154,6 +154,27 @@ describe('orchestration worker launch preferences', () => {
).not.toThrow()
})
it('refuses --retry-of beside --spec, which could only create a fresh Task', () => {
const parsed = WorkerStartParams.safeParse({
spec: 'redo it',
retryOf: 'ctx_prior',
agent: 'claude',
from: 'term_coord'
})
expect(parsed.success).toBe(false)
expect(parsed.error?.issues.map((issue) => issue.message)).toContain(
'--retry-of needs --task <task_id> naming the failed Task; --spec creates a new one'
)
expect(
WorkerStartParams.safeParse({
task: 'task_1',
retryOf: 'ctx_prior',
agent: 'claude',
from: 'term_coord'
}).success
).toBe(true)
})
it('uses the requested launch receipt when an older worker omits it', () => {
const requested = createPendingWorkerLaunchReceipt({
agent: 'codex',
@@ -48,6 +48,16 @@ export const WorkerStartParams = z
message: '--task and --spec are mutually exclusive'
})
}
// Why: --spec creates a new Task, so a retry link to a prior Dispatch could never resolve and
// the refusal named a Task id the caller never supplied.
if (params.retryOf && params.spec) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['retryOf'],
message:
'--retry-of needs --task <task_id> naming the failed Task; --spec creates a new one'
})
}
})
export type WorkerStartInput = z.infer<typeof WorkerStartParams>