Skip to content

feat(cli): pause at human gates without a terminal; tracker answer - #735

Open
clintecker wants to merge 19 commits into
feat/pause-gate-corefrom
feat/tracker-answer
Open

clintecker wants to merge 19 commits into
feat/pause-gate-corefrom
feat/tracker-answer

Conversation

@clintecker

@clintecker clintecker commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

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.
    • With no gate automation flag, a run with no terminal on stdin, or with --json (agent harnesses may allocate a PTY), now pauses at a human gate.
    • console is the old stdin prompt; pause always pauses and implies --no-tui.
    • console / pause combined with --auto-approve, --webhook-url or --autopilot is a usage error.
  • Exit 4 for paused_gate. paused_billing stays at 1; exitCodeFor centralizes the mapping. 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, --node when several gates wait.
  • Invocation record in the secure state dir (invocation.json): argv minus -r / --from, cwd, and pipeline.GraphDigest of the loaded workflow and subgraphs. Sidecars are resolved into node attrs, so editing one changes the digest.
  • tracker answer.
    • With no run ID it lists waiting runs; with a run ID it shows the gate.
    • With an answer — a label, --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.
  • Docs. 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 console restores stdin), glossary, architecture status table, AGENTS.md / llms.txt (what an agent does on exit 4), handlers/human.md, CLAUDE.md.

Verification

  • Unit: gateModeFor table, --gates conflicts, 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.
  • End to end with the built binary and the fake claude:
    • quickfix pause → revise → --text → approve, committing to tracker/quickfix/<id>. --param change reached both MakeChange prompts and the checkout is untouched.
    • --default rejects; refusals (typo, bad answers file, unknown --node); list.
    • Digest refusal after editing the .dip (mutation-checked).
    • --json stream with exactly one terminal event (gate_paused) and no failure events.
    • Console -r resume after a pause, after which tracker answer reports "not waiting".
  • go test ./... green; make doctor all A; make dippin-test green; golden traces unchanged; make docs-check (16 commands) green.

Stacked on #734. I'll rebase onto main once #733 and #734 merge.

Closes #732

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-20T23:17:17.785011Z
We couldn’t complete this review. Comment @codex review to try again.
ad71b20 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8b33ce7b-b27c-43ed-964b-729e51539799

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

clintecker added a commit that referenced this pull request Oct 20, 2026
…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>
clintecker and others added 10 commits October 20, 2026 18:42
… 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>
@clintecker
clintecker force-pushed the feat/pause-gate-core branch from 266e1a8 to 961513d Compare October 20, 2026 23:45
clintecker added a commit that referenced this pull request Oct 20, 2026
…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>
clintecker and others added 9 commits October 21, 2026 12:37
…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

This branch has not been deployed

No deployments
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