Repository navigation
feat(cli): pause at human gates without a terminal; tracker answer - #735
clintecker wants to merge 19 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…e answer command, stripped gate text - --node is replaced by a repeatable --answer <gate>=<answer>: when parallel branches wait at several gates, every one is answered in the same call (a freeform gate's non-label answer is its free text); a single answer with several waiting is refused, naming them. - The summary and tracker answer <id> print one command from pipeline.AnswerCommand (the same one the gate_paused event carries). - Gate labels, notes, prompts and the waiting-run list are printed with control characters stripped: prompts carry LLM output. - CHANGELOG: --json at a terminal now pauses too; docs say --answer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ad71b20 to
0114560
Compare
… mirror A PauseError can carry the waiting gate(s) (PausedGate: the gate's exact interviewer arguments, full prompt, fingerprint and nonce). The engine stores them on Checkpoint.PausedGates before saving, stamps the cumulative usage on PriorUsage, writes a 0600 gate.json mirror in the run dir (symlink refused, atomic rename; never read back), and puts the gate on the gate_paused event (GateDetail.Resolution "paused" + Fingerprint, also on the activity log and the NDJSON stream). The record and mirror clear when the paused node completes; a run start with no waiting gate removes a stale mirror. buildSuccessResult moves to terminal_emit.go for the size ratchet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The human handler builds the gate's GateInfo once (now with the full prompt, ScopePath and BranchIndex) and passes it to the interviewer on the gate call's context (WithGateInfo / GateInfoFrom) as well as to BeginGate, so an interviewer serving concurrent branch gates reads the right gate from the call. Subgraph and manager_loop children run under WithSubgraphScope; the parallel handler marks each branch context with internal branch scope and index keys (internal, so they never leak past the join). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nswer once PausingInterviewer records exactly what each gate asked (its Ask* arguments, the GateInfo from the call's context, interview questions incl. context, a fingerprint and a fresh nonce) and pauses with NewPausedGateError, or returns the in-memory PendingAnswer once for the gate with the same node, scope and fingerprint. A changed question drops the answer and re-pauses with a note. In pause mode a gate inside a subgraph / manager_loop child fails closed (routable OutcomeFail); a paused gate's gate_resolved carries Resolution "paused" and no Error. GateQuestion gains Context. CallbackInterviewer and QueueInterviewer move to interviewer_callback.go for the size ratchet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…first A parallel node whose branches pause at gates returns one paused_gate pause carrying every branch's gate record, ordered by branch index (it used to keep only the first branch's pause). A billing pause in any branch outranks gate pauses and carries no gate record: the resume re-runs the node and its gates ask again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resuming a run whose checkpoint has PausedGates skips resume compaction (the gate must re-render the question it recorded, or the answer never matches) and seeds the budget guard's baseline from PriorUsage, so the cost and token caps bound the whole run across answers. Other resumes are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Config.GateMode ("pause" / "console", after Interviewer / AutoApprove /
WebhookGate / Autopilot; pause requires ArtifactDir). tracker.Answer resumes a
paused_gate run with an answer validated against the checkpoint's record
(ValidateAnswer: per-mode rules, case-insensitive labels, errors list the valid
answers), re-checking the record's nonce under the run lock; WaitingRuns and
PendingGates read the authoritative checkpoint. Every ResumeRunID run holds a
per-run lock in the secure dir (pipeline.AcquireRunLock: O_EXCL + PID, stale
locks replaced). Result / RunSummary gain PausedGate. Docs: embedding,
transport boundary, human handler (pause mode), engine (pausing), API
stability, site transport page. Size-ratchet moves: resume setup to
tracker_resume.go, terminal result builders to terminal_emit.go.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rprint Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CLI's -r resolves the run to Config.CheckpointDir rather than setting ResumeRunID, so the lock keyed only on ResumeRunID missed it. Any existing checkpoint the run resumes from now takes the lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed payload, stale records, OS run lock - Several gates waiting in one parallel node looped forever when answered one at a time: each answer re-ran the node, the unanswered sibling paused again and the given answer was discarded. The pausing interviewer now holds one answer per gate (NewPausingInterviewerFor), and tracker.ValidateAnswers requires an answer for every waiting gate (AnswerInput.Gates, keyed by node ID or <scope>/<node>); a single answer with several waiting is refused, naming them. A branch gate that is its branch's target no longer repeats its node ID in its scope path. AnswerInput.Node is gone (unreleased). - gate_paused carries the waiting gate's node and scope, how many gates wait, and the tracker answer command (pipeline.AnswerCommand), in GateDetail, the event message, the activity log and the NDJSON stream. - A run that ends any way but paused at a gate drops its waiting-gate record and gate.json (a failure at the gate, a --from resume around it). - The run lock is an OS file lock (flock / LockFileEx), released by the OS when the holder exits, so no stale-lock takeover (and no race in it). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
266e1a8 to
961513d
Compare
…e answer command, stripped gate text - --node is replaced by a repeatable --answer <gate>=<answer>: when parallel branches wait at several gates, every one is answered in the same call (a freeform gate's non-label answer is its free text); a single answer with several waiting is refused, naming them. - The summary and tracker answer <id> print one command from pipeline.AnswerCommand (the same one the gate_paused event carries). - Gate labels, notes, prompts and the waiting-run list are printed with control characters stripped: prompts carry LLM output. - CHANGELOG: --json at a terminal now pauses too; docs say --answer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
0114560 to
fac0a65
Compare
…ly gate reads, answer command flags, interrupted resume keeps the gate - A parallel node whose answered gates re-pause one at a time (a changed question) alternated between its gates forever: the re-pause recorded only the gate that asked again. It now records the node's other gates again too (withAnsweredSiblings) — their answers were used, and the node re-runs. - PendingGates / WaitingRuns / Answer read only the secure checkpoint: a checkpoint planted in the tool-writable run dir is never listed or resumed. - The gate_paused answer command carries the run's -w / --artifact-dir (WithAnswerCommandFlags, set by the library from WorkingDir / ArtifactDir) and no shell comment; AnswerAlternatives lists the other answers. - A canceled resume (Ctrl+C during an interactive tracker -r) keeps the waiting-gate record instead of dropping it with the fail terminal. - Size-ratchet moves: generateRunID to engine_run_identity.go, the budget usage helpers to budget.go (baseline shrinks). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
…only Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
With no gate automation flag, a run without a terminal on stdin (or with --json, which an agent harness may run under a PTY) now pauses at its first human gate instead of reading the answer from stdin. --gates auto (default) | console (the old stdin prompt) | pause (also implies --no-tui). --gates console/pause with --auto-approve, --webhook-url or --autopilot is a usage error. runOptions carries the held answer a tracker answer resume applies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A paused_gate run exits 4 (waiting for an answer; paused_billing stays 1). The summary prints the waiting gate — label, options, note, full prompt — with no failure banner and no tracker -r hint, and ends with the exact tracker answer command (-w / --artifact-dir repeated when given, --node when several gates wait). The run's invocation (args minus -r/--from, cwd, and pipeline.GraphDigest of the loaded workflow and subgraphs) is recorded in the secure state dir so tracker answer can rebuild it. exitCodeFor centralizes the exit-code mapping; result interpretation moves to run_result.go. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tracker answer with no run ID lists runs waiting at a gate; with a run ID it shows the gate (from the checkpoint, never gate.json) and the command that answers it; with an answer (a label, --text / text after --, --default, --answers-file, --cancel, --node for one of several branch gates) it validates against the recorded gate, rebuilds the run from its invocation record (same cwd, flags and params), refuses when the workflow's digest changed, re-checks the gate's nonce once the engine holds the run lock, and resumes in plain mode holding the answer in memory. A later gate pauses the run again (exit 4). Documented in cli.html (command, --gates, exit code 4) and the CLI help. tracker.SelectGate / CheckStillWaiting are exported for it. End-to-end tests drive quickfix (revise, free text, approve; --default) on the fake claude, the --json stream (one gate_paused terminal), a digest refusal and a console resume after a pause. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
README (pausing section, command list, --gates), CHANGELOG (Added: tracker answer + pause mode; Changed: no-terminal gates pause, --gates console for the old stdin prompt), glossary (paused gate), architecture status table (paused_gate), AGENTS.md / llms.txt (what an agent does when a run exits 4), handlers/human.md, CLAUDE.md (pause-mode rules, CLI command). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e answer command, stripped gate text - --node is replaced by a repeatable --answer <gate>=<answer>: when parallel branches wait at several gates, every one is answered in the same call (a freeform gate's non-label answer is its free text); a single answer with several waiting is refused, naming them. - The summary and tracker answer <id> print one command from pipeline.AnswerCommand (the same one the gate_paused event carries). - Gate labels, notes, prompts and the waiting-run list are printed with control characters stripped: prompts carry LLM output. - CHANGELOG: --json at a terminal now pauses too; docs say --answer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tracker answer can't rebuild a run whose invocation record is missing — one a program using the library started, or one whose process died between saving the paused checkpoint and recording its invocation. The error now says to answer a library run with tracker.Answer, or to resume with tracker -r and the run's flags (it pauses at the gate again and records them). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…esume hint that keeps the flags, --gates console note - tracker answer from another directory, or through a symlinked spelling of the run's own, was refused as "the workflow changed": after chdir, os.Getwd resolves symlinks when $PWD names another directory, so the loaded workflow_dir differed. The digest leaves out workflow_dir (where the workflow sits, not what it does) and tracker answer sets PWD after chdir. - A digest refusal now prints cd <dir> && tracker -r <id> <the run's own arguments>, shell-quoted, instead of a bare -r that dropped --param, budgets and backend. - A run that paused only because stdin is not a terminal (--gates auto) says --gates console reads answers from stdin instead. - The summary and tracker answer <id> list the other answers above the answer command, which stays the last line and is a plain command. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01647BKi372JDvjUGxQeYU9g
fac0a65 to
0bf7053
Compare
5ed4be0 to
ec0347e
Compare
Problem
Last of three PRs for #732. PRs #733 and #734 can pause a gate and answer it from the library, but the CLI still reads gates from stdin without a terminal. An agent's shell, CI or a script therefore can't drive a gated workflow such as
quickfix.Fix
--gates auto|console|pause.--json(agent harnesses may allocate a PTY), now pauses at a human gate.consoleis the old stdin prompt;pausealways pauses and implies--no-tui.console/pausecombined with--auto-approve,--webhook-urlor--autopilotis a usage error.paused_gate.paused_billingstays at 1;exitCodeForcentralizes the mapping. The summary prints the waiting gate (label, options, note, full prompt) with no failure banner and notracker -rhint, and ends with the exacttracker answercommand:-w/--artifact-dirrepeated,--nodewhen several gates wait.invocation.json): argv minus-r/--from, cwd, andpipeline.GraphDigestof the loaded workflow and subgraphs. Sidecars are resolved into node attrs, so editing one changes the digest.tracker answer.--text, text after--,--default,--answers-file,--cancel,--node— it validates against the checkpoint record, rebuilds the run from the invocation record, refuses a changed workflow, re-checks the gate's nonce once the engine holds the run lock, and resumes in plain mode with the answer held in memory.cli.html(command,--gates, exit code 4), CLI help, README (new "Pausing at human gates" section), CHANGELOG (Added, plus Changed: no-terminal gates pause;--gates consolerestores stdin), glossary, architecture status table,AGENTS.md/llms.txt(what an agent does on exit 4),handlers/human.md, CLAUDE.md.Verification
gateModeFortable,--gatesconflicts, interviewer selection (the old "non-TTY → console" case became "non-TTY → pause" plus "--gates console→ console"), pause implies--no-tui,interpretRunResult/exitCodeFor, paused summary (last line = answer command), answer-command shapes, invocation round trip,stripResumeArgs,GraphDigest, answer argument parsing (incl.--text), answers-file schema, list/show.claude:revise→--text→approve, committing totracker/quickfix/<id>.--param changereached both MakeChange prompts and the checkout is untouched.--defaultrejects; refusals (typo, bad answers file, unknown--node); list..dip(mutation-checked).--jsonstream with exactly one terminal event (gate_paused) and no failure events.-rresume after a pause, after whichtracker answerreports "not waiting".go test ./...green;make doctorall A;make dippin-testgreen; golden traces unchanged;make docs-check(16 commands) green.Stacked on #734. I'll rebase onto
mainonce #733 and #734 merge.Closes #732
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.