Skip to content

F: wire the runner and startup recovery into the server (#91, part 1) - #102

Merged
mchwang merged 12 commits into
mainfrom
feat/f-runner-wiring-91
Oct 3, 2026
Merged

mchwang merged 12 commits into
mainfrom
feat/f-runner-wiring-91

Conversation

@mchwang

@mchwang mchwang commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Part 1 of #91, built to the decisions posted on #91: #91 (comment)

What this does

An optional runner block in review.json turns on the production runner. Without the block, and in every demo, startup is unchanged, and runner actions say the runner is not configured.

With the block, startServer runs setUpRunner (runner/production.ts) after the Store opens and before the server listens. It:

  1. Refuses to start without a github block (the prompt carries the issue) or CLAUDE_CODE_OAUTH_TOKEN.
  2. Calls lock.verify(), then reads the database's runner token for the locked file.
  3. Runs recoverStartup with D's own recoverLeftovers, exportTaskDiff and removeTaskFilesystemsAsync. Every unowned Docker object blocks startup, as runner-lifecycle.md requires. The message lists the exact docker … rm lines.
  4. Builds the agent image after D's recovery has stopped leftover agents, and before any export's deadline starts.
  5. Assembles the execution deps: the real TaskWorkspace, a Claude launcher, and the issue text with collaborators' comments only (GhIssueGateway.issueText).

Any failure closes the Store, releases the lock, and prints only the message.

Other changes:

  • Schema v9. Each allocation's metadata_baseline and storage_base are saved as soon as D's allocation returns, so recovery can export a recovered storage. A handle is used only if its attempt ID and allocation ID both match a row. Diffs go through saveDiagnostic; retention keeps every diff the same recovery saved, and a cut diff says so.
  • Carry-overs from F2b: connect the real workspace (partial export, failed-run audit, durable safety findings, commit export) #87:
    • RunnerDeps.kinds: admission refuses kinds the deps cannot run (execute only).
    • ItemExecutor.close(): shutdown awaits plan runs before the Store closes.
    • Every shutdown step runs even when an earlier one fails.
    • Resuming a task that stopped between items comes in part 2.
  • /api/runner. It refuses to retry a plan item outside ItemExecutor; its view offers retry only when admission would take it.
  • CLI:
    • --release-preparation <attempt ID>, which a recovery message points to.
    • "Still stopping agents…" on a second Ctrl+C during shutdown.
    • Ctrl+C during startup: the first waits for startup to finish; the second stops at once.
    • Refusals are printed as messages, not stack traces.
  • ReviewService.planContext reads a base commit's tree once, and the runner reuses the issue text across a run's items for up to five minutes, so items do not run git or gh again on the server thread.

Docs: runner-lifecycle.md ("Production wiring (#91)", shutdown step 5a, unowned-resource messages) and the README section "Runner (opt-in)".

Not in this PR (part 2): /api/runner start and resume, including a task stopped between items, and the screen's buttons.

Validation

  • npm run typecheck: clean.
  • vitest without the six real-Docker files: 55 files, 1,264 tests, all passing, on the merge with main at dd2fdac (head 55d3d5c); 1,292 tests at head 15db8db after the later rounds.
  • Real-Docker files not run locally: agent-container, agent-gate, agent-network, agent-question, agent-supervisor and runner-workspace. The Docker CLI hangs in the authoring shell. CI's real-docker job is their first run against this branch; runner-workspace covers the new baseline save. Watch it to the end before merging, because that job is not required.

Review rounds

Independent review agents (fresh context, contract checklist), then /code-review high in a loop until a round found nothing new.

Round Found Outcome
Agent 1 The schema was unreadable under umask 077; recovery retention deleted diffs from the same pass; tests missing (baseline save, D adapters, close order); retry bypassed the executor; unowned lines could not be pasted; issue-text limits; image built before leftover agents were stopped; shutdown skipped steps after a failure; CLI stack traces All fixed (b2acf8b)
Agent 2 The image build used up the first export's deadline; shutdown failure handling; retryable offered a refused retry; CLI stack traces; quoting All fixed (1e115c3)
Agent 3 The view ignored kinds and unreleased; later shutdown failures were dropped; doc wording Fixed (a5bb143)
/code-review high 1 Blocking git per item; issue refetched per item; cut diff not marked; Ctrl+C during recovery; duplicate docker rm builder; awkward RecoveryBlocked separator; CLI untested All fixed (4848786)
/code-review high 2 Wall-clock reuse window; Ctrl+C message promised too much; startup could not be interrupted All fixed (8d1052a)
/code-review high 3 Demo refusal gave advice that changed nothing Fixed (3da01d8)
/code-review high 4 Nothing new —

Declined, each fails closed:

  • A baseline write refused after the shutdown gate closes loses only that attempt's partial output.
  • The issue number is read from the latest plan revision; prepareExecution refuses a mismatch.
  • More than 1,000 comments blocks the runner for that issue, because GitHub's API cannot filter by author.
  • A cold image build can come before an unowned-object refusal; that wastes time but changes nothing.
  • Startup errors without a runner block still print their stack, as before this branch.

Review lessons (merge gate)

  • Umask narrows a create mode → one-off: the profile's readability check now has a test under umask 077.
  • A view offering what the action refuses → covered by AGENTS.md, "disable retry … when it cannot run".
  • A blocking call before an armed timer eats the deadline → one-off, specific to buildAgentImage being synchronous; a test pins the order.
  • Retention keeping only the latest save → one-off: the keep list now covers every unreferenced save in the pass.
  • A shutdown step skipped after an earlier failure → covered by AGENTS.md, "cancel and await jobs, then close storage".

Later rounds (after the PR opened)

Round Found Outcome
Merge with main (#100, #101) D now requires the pre-launch tree check for every execute start; the launcher dropped it Fixed (ccbd9a2)
/code-review high 5 A runner root with a comma or line break failed every launch; the launcher test checked copied rules, not D's reader Fixed (d691236)
Copilot 1 Issue limits exceeded the prompt's 32 KiB data budget; inherited names were accepted as storage limits; README overstated the diagnostics cap; Ctrl+C during startup was untested Fixed (6858c2b)
Copilot 2 The diagnostics directory was shared across databases, so retention could delete another database's diffs; --release-preparation without --config printed help and exited 0; a demo config opened with --config ran its runner block Fixed (15db8db)

Review lessons from these rounds:

  • A git merge with no conflict can still break a changed contract (the tree check). Covered by AGENTS.md: "Treat every behavioural claim… as something to verify."
  • Two bounds for the same payload that disagree (issue limits and prompt budget). Covered by AGENTS.md: "Align subprocess output limits with every payload the schema accepts…"
  • in matching inherited keys. One-off: an own-key check plus a test.
  • State shared across databases when the lock is per database (diagnostics). One-off: diagnostics are namespaced by the runner token.
  • A decision read from a CLI flag instead of the loaded config (demo). One-off: a child-process test.
  • An untested signal lifecycle. Covered by AGENTS.md "Required race regressions" (shutdown interleavings).

Copilot 3 (review summary, "previously missed", no inline threads). All are real and low severity, so they are deferred to #105, which builds on this branch and is still open:

🤖 Generated with Claude Code

mchwang and others added 8 commits October 1, 2026 16:26
An opt-in `runner` block in review.json turns on the production runner. After
the Store opens and before the server listens, setUpRunner verifies the lock,
reads the database's runner token, builds the agent image and runs startup
recovery with D's own recoverLeftovers, exportTaskDiff and removal. It then
assembles the execution deps over the real workspace and a Claude launcher.

- Schema v9 saves each allocation's metadataBaseline and seeded commit, so
  recovery can export a recovered storage. Handles must match the row's
  attempt and allocation IDs. Diffs go through saveDiagnostic.
- RunnerDeps.kinds: admission refuses attempt kinds the deps cannot run.
- ItemExecutor.close(): shutdown awaits plan runs in progress before the
  Store closes.
- GhIssueGateway.issueText: the issue's title, body and collaborators'
  comments, bounded, for the execute prompt.
- CLI: --release-preparation; "Still stopping agents…" on a second Ctrl+C;
  refused runner startups print their message and exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The execute schema is chmod 0444 after writing: a restrictive umask
  made it unreadable to the container user, failing every launch.
- Recovery's retention keeps every diff the same recovery saved; before,
  the second save could delete the first before finalization referenced it.
- The agent image is built after D's recovery stopped leftover agents.
- Unowned objects are listed one shell line each, the reason as a comment.
- Issue text drops other people's comments before checking their body, and
  exactly 1000 comments no longer fails.
- /api/runner refuses to retry an execute attempt outside ItemExecutor.
- A failing shutdown step no longer skips stopping the runner or closing
  the Store; --release-preparation prints its error as a message.
- Tests: materialize saves the baseline; D adapter arguments; server close
  order with the executor; retry refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Build the agent image as soon as D's recovery returns storage, before
  recoverStartup arms the first export deadline: the build blocks, so a
  cold build used up that deadline and lost the partial output.
- Shutdown runs every step even when one fails, awaiting plan runs before
  the Store closes, and reports the first failure, not a later one.
- /api/runner's view no longer offers retry for an execute attempt that
  the action refuses.
- CLI refusals before the lock (bad port, config, runner block) print
  their message instead of a stack.
- Unowned object names are shell-quoted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d take it (#91)

- retryable also requires the deps to run the attempt's kind and no
  unconfirmed cleanup (runner.unreleased), as admission does.
- Later shutdown failures are logged, not dropped behind the first.
- dRecoveryDeps keeps its doc comment; startup order wording fixed in
  setUpRunner's comment and the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ReviewService.planContext reads a base commit's tree once: each runner
  item no longer runs blocking git on the server's thread.
- The runner reuses the issue text across a run's items for up to five
  minutes instead of rereading every collaborator and comment page.
- A partial diff D cut at its limit ends with a codeboost: notice line.
- A Ctrl+C during startup takes effect after startup recovery finishes.
- removalCommand is shared by the runner's and Ask's leftover messages;
  RecoveryBlocked lists its items one per line.
- CLI tests run web/cli.ts: runner refusal, bad port, release-preparation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The issue reuse window runs on the monotonic clock.
- A second Ctrl+C during startup stops at once (recovery reruns at the
  next start); the first says so, and that a running image build stops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Demos never run the runner, so telling a demo to add a runner block
was advice that changed nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the conflicts with #100 (gitlink mounts and the pre-launch tree
check) and passes the tree check through claudeLauncher: D requires it
for an execute start, so without it every production launch would fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Issue payload limits conflict with the execution prompt limit, and the new startup signal lifecycle lacks regression coverage.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 2 Low severity

Open (4)
What changed in this PR

Wires the opt-in production runner, startup recovery, and schema v9 persistence into the server.

Changes:

  • Adds production runner assembly, recovery, locking, CLI controls, and shutdown coordination.
  • Persists storage baselines and improves diagnostic retention.
  • Adds GitHub issue-text retrieval and extensive runner lifecycle tests.
File Description
web/​server.ts Wires runner setup and shutdown.
web/​cli.ts Adds runner CLI lifecycle.
runner/​production.ts Builds production runner dependencies.
runner/​recovery.ts Extends startup recovery.
runner/​workspace.ts Persists allocation baselines.
runner/​store.ts Adds schema v9 fields.
runner/​execution.ts Adds issue fetching and executor shutdown.
runner/​coordinator.ts Restricts supported attempt kinds.
runner/​diagnostics.ts Handles truncated output and retention.
runner/​review.ts Caches base-tree entries.
runner/​question-container.ts Quotes recovery commands.
github/​issues.ts Retrieves bounded issue text.
README.md Documents runner configuration.
docs/​implementation/​runner-lifecycle.md Documents production lifecycle.
test/​runner-workspace-baseline.test.ts Tests baseline persistence.
test/​runner-shutdown.test.ts Updates runner refusal expectations.
test/​runner-recovery.test.ts Expands recovery coverage.
test/​runner-production.test.ts Tests production assembly.
test/​runner-lifecycle-store.test.ts Tests schema v9 storage.
test/​runner-execution.test.ts Tests executor shutdown and issue failures.
test/​runner-coordinator.test.ts Tests kind restrictions.
test/​review.test.ts Tests base-tree caching.
test/​question-agent.test.ts Updates quoted command expectations.
test/​publish.test.ts Updates schema-version expectations.
test/​issues.test.ts Tests issue-text retrieval.
test/​cli.test.ts Tests CLI refusal handling.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread github/issues.ts Outdated
Comment thread runner/production.ts Outdated
Comment thread README.md Outdated
Comment thread web/cli.ts
mchwang and others added 2 commits October 2, 2026 20:29
…ma with D's reader (#91)

A comma or line break in runner.root or diagnosticsDir passed the config
and then failed every launch in D's mount check; it is refused at config
time now. The launcher test also reads schema.json through D's own
readCapturedFile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- issueText checks the issue against the execute prompt's own budget and
  serializer (dataJSON, 32 KiB), so an issue it accepts is one a prompt can
  carry; a larger one is refused at the fetch with the reason.
- Storage limits must be own keys: inherited names such as toString are
  refused like any unknown limit.
- README: the diagnostics cap is a retention target, not a hard limit.
- CLI tests drive Ctrl+C during startup in a child process held by a fake
  docker: the first waits and exits 0 after startup, the second exits 130.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Demo configuration handling and shared diagnostics retention can cause startup failure or cross-database data loss.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
Resolved since last review (4)

Comment thread runner/production.ts Outdated
Comment thread web/cli.ts Outdated
Comment thread web/cli.ts Outdated
- Diagnostics live per database, in <diagnosticsDir or root>/<runnerOwner>/
  diagnostics: retention reads references from one Store only, so runners
  of databases sharing a root never delete each other's diffs.
- --release-preparation without --config reports that it needs one,
  instead of printing help and exiting 0.
- A demo configuration ignores its runner block however it is opened
  (config.demo, not the --demo flag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Untrusted filesystem names can forge CLI output, and one configured startup failure still emits a stack trace.

Review effort: Balanced
Findings: None

Resolved since last review (3)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Reject oversized prompts before fetching comments

github/​issues.ts:258

When the title and body alone already exceed the prompt budget, this running total is not checked until a collaborator comment is encountered. With no retained comments, the method fetches every comment page before the final serializer rejects; in production those unnecessary calls can instead hit the overall timeout. Reject immediately once the initial total is over the limit.

Medium severity Quote untrusted filesystem names in CLI diagnostics

web/​cli.ts:50

unknownEntries contains filesystem names and Linux names may include newlines or terminal control characters. Interpolating the path directly lets an untrusted entry forge additional CLI output. Quote it as structured text, consistent with other agent-controlled paths and diagnostics.

Medium severity Route lock verification failures through message-only handling

web/​cli.ts:68

The configured-runner message-only handling covers only rejections from startServer. The later lock.verify() can still fail if the database path changes during recovery/startup; its catch rethrows, so this configured runner failure prints a stack despite the PR's refusal contract. Route that catch through the same message-only path after closing the app and releasing the lock.

@mchwang
mchwang merged commit 80ac5f2 into main Oct 3, 2026
3 checks passed
mchwang added a commit that referenced this pull request Oct 3, 2026
main's tree equals this branch's part 1 commit 15db8db, which the branch
already contains, so every conflict resolves to this branch's side; the
merged tree is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Oct 3, 2026
- issueText refuses a title and body already over the prompt budget
  before reading any collaborator or comment page.
- Recovery warnings (recoveryWarnings) quote Docker labels and file
  names, so a newline or control character cannot forge a line of output.
- A lock.verify failure after startup prints its message and exits 1,
  not a stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Oct 3, 2026
…105)

* F: wire the runner and startup recovery into the server (#91, part 1)

An opt-in `runner` block in review.json turns on the production runner. After
the Store opens and before the server listens, setUpRunner verifies the lock,
reads the database's runner token, builds the agent image and runs startup
recovery with D's own recoverLeftovers, exportTaskDiff and removal. It then
assembles the execution deps over the real workspace and a Claude launcher.

- Schema v9 saves each allocation's metadataBaseline and seeded commit, so
  recovery can export a recovered storage. Handles must match the row's
  attempt and allocation IDs. Diffs go through saveDiagnostic.
- RunnerDeps.kinds: admission refuses attempt kinds the deps cannot run.
- ItemExecutor.close(): shutdown awaits plan runs in progress before the
  Store closes.
- GhIssueGateway.issueText: the issue's title, body and collaborators'
  comments, bounded, for the execute prompt.
- CLI: --release-preparation; "Still stopping agents…" on a second Ctrl+C;
  refused runner startups print their message and exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the gaps review round 1 found in the runner wiring (#91)

- The execute schema is chmod 0444 after writing: a restrictive umask
  made it unreadable to the container user, failing every launch.
- Recovery's retention keeps every diff the same recovery saved; before,
  the second save could delete the first before finalization referenced it.
- The agent image is built after D's recovery stopped leftover agents.
- Unowned objects are listed one shell line each, the reason as a comment.
- Issue text drops other people's comments before checking their body, and
  exactly 1000 comments no longer fails.
- /api/runner refuses to retry an execute attempt outside ItemExecutor.
- A failing shutdown step no longer skips stopping the runner or closing
  the Store; --release-preparation prints its error as a message.
- Tests: materialize saves the baseline; D adapter arguments; server close
  order with the executor; retry refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the gaps review round 2 found in the runner wiring (#91)

- Build the agent image as soon as D's recovery returns storage, before
  recoverStartup arms the first export deadline: the build blocks, so a
  cold build used up that deadline and lost the partial output.
- Shutdown runs every step even when one fails, awaiting plan runs before
  the Store closes, and reports the first failure, not a later one.
- /api/runner's view no longer offers retry for an execute attempt that
  the action refuses.
- CLI refusals before the lock (bad port, config, runner block) print
  their message instead of a stack.
- Unowned object names are shell-quoted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review round 3: the runner view offers retry only when admission would take it (#91)

- retryable also requires the deps to run the attempt's kind and no
  unconfirmed cleanup (runner.unreleased), as admission does.
- Later shutdown failures are logged, not dropped behind the first.
- dRecoveryDeps keeps its doc comment; startup order wording fixed in
  setUpRunner's comment and the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review (high) round 1 fixes for the runner wiring (#91)

- ReviewService.planContext reads a base commit's tree once: each runner
  item no longer runs blocking git on the server's thread.
- The runner reuses the issue text across a run's items for up to five
  minutes instead of rereading every collaborator and comment page.
- A partial diff D cut at its limit ends with a codeboost: notice line.
- A Ctrl+C during startup takes effect after startup recovery finishes.
- removalCommand is shared by the runner's and Ask's leftover messages;
  RecoveryBlocked lists its items one per line.
- CLI tests run web/cli.ts: runner refusal, bad port, release-preparation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review (high) round 2 fixes for the runner wiring (#91)

- The issue reuse window runs on the monotonic clock.
- A second Ctrl+C during startup stops at once (recovery reruns at the
  next start); the first says so, and that a running image build stops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review (high) round 3: demos get their own runner refusal (#91)

Demos never run the runner, so telling a demo to add a runner block
was advice that changed nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* F: start and resume a task's plan through /api/runner (#91, part 2)

- start runs a task that is in review or queued and has not begun at its
  plan revision; an in-review task moves to queued in the same action.
- resume continues a begun or requeued task from its first unfinished
  item (completed, failed or stopped last item alike) and claims the
  requeue in the admitting transaction, which also covers a task stopped
  between items.
- ItemExecutor.begin admits the first item synchronously inside the user
  action, throwing the refusal so the action's own writes roll back;
  runTask keeps reporting it as not started.
- GET /api/runner adds startable and resumable from the same checks.
- Retrying an execute attempt points to resume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the gaps review round 1 found in start and resume (#91)

- A plan revised after it started running is refused by both actions,
  with #88 named, instead of each pointing at the other.
- An owed safety finding settles only once the escalation commits: a
  user action rolling back after begin keeps it owed.
- runChoice also refuses a closed task first, a runner that cannot run
  execute attempts, an active merge and a spent task budget, so the
  view's startable and resumable match the actions.
- Tests: the settled-finding outcome, budget and revision refusals, replay
  and stale version, shutdown, begin under a rolled-back action; the
  between-items setup saves result as production does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Close the gaps review round 2 found in start and resume (#91)

- A spent budget is refused by admission again, not by runChoice, so the
  refusal's effect still moves the idle task to needs human; the view
  alone checks the budget.
- The #88 refusal applies only when an earlier revision's items made
  runner commits; a revised plan whose earlier attempts committed nothing
  resumes from its first item.
- The view and runChoice see a run still finishing in ItemExecutor, and
  the view no longer hides a storage error as "not offered".
- Tests: the shutdown case now reaches the action (rejectAdmission with the
  server open); budget asserts the needs-human move; the settled finding
  is no longer owed after the commit; both revision cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review round 3: one start/resume rule in code and docs (#91)

- start needs no attempt at the current plan revision; resume needs one
  at any revision (or a requeue). An in-review task whose only attempts
  were at an earlier, uncommitted revision can start again.
- The doc and runChoice comment state that rule.
- A strict begin throws on a revision change instead of reporting a
  settled run.
- Tests: an earlier item completed unchanged is no commit and reruns at
  the new revision; a 503 start is not recorded and its action ID is
  refused afresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review round 4: tests for the start rule, doc precision (#91)

- Tests: an in-review task whose only attempts were at an earlier,
  uncommitted revision can start; a queued one is offered both actions,
  which run the same item; the unchanged-item test checks the revision.
- Doc: start also refuses a pending requeue; a spent budget moves only a
  running or queued task to needs human.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Status polls read run progress with one targeted query (#91)

Copilot review on #105: progress() loaded and decoded every attempt
(results up to 1 MiB) twice per /api/runner poll. Store.executeProgress
now aggregates only the execute attempts' item, state, plan revision and
unchanged flag in SQL, and the view computes it once for both flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review: refuse a runner root Docker cannot mount; check the schema with D's reader (#91)

A comma or line break in runner.root or diagnosticsDir passed the config
and then failed every launch in D's mount check; it is refused at config
time now. The launcher test also reads schema.json through D's own
readCapturedFile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address Copilot review on #102 (#91)

- issueText checks the issue against the execute prompt's own budget and
  serializer (dataJSON, 32 KiB), so an issue it accepts is one a prompt can
  carry; a larger one is refused at the fetch with the reason.
- Storage limits must be own keys: inherited names such as toString are
  refused like any unknown limit.
- README: the diagnostics cap is a retention target, not a hard limit.
- CLI tests drive Ctrl+C during startup in a child process held by a fake
  docker: the first waits and exits 0 after startup, the second exits 130.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address Copilot review round 2 on #102 (#91)

- Diagnostics live per database, in <diagnosticsDir or root>/<runnerOwner>/
  diagnostics: retention reads references from one Store only, so runners
  of databases sharing a root never delete each other's diffs.
- --release-preparation without --config reports that it needs one,
  instead of printing help and exiting 0.
- A demo configuration ignores its runner block however it is opened
  (config.demo, not the --demo flag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review round 1 on #105: count every writable kind's earlier commit; clearer start refusal; tests that fail when broken (#91)

- earlierCommits counts fix and rebase-fix commits too, as hasRunnerCommit does, and a result that is not valid JSON counts as a commit (fail closed).
- start names the task's status, not resume, for a task resume cannot run either.
- The rollback test now reaches the move to queued (admission refuses a spent budget) and replays the recorded refusal.
- New tests: begin's in-flight guard between items, an owed scope pause on resume, an owed finding on start from in review, a fix attempt's earlier commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review round 2 on #105: point a running task's start to resume; tests for the queue move and paying nothing at shutdown (#91)

- start points to resume for a running task that ran only an earlier revision, where resume runs.
- Tests: start from in review queues before it escalates an owed finding; begin pays nothing owed once admission closed.
- Docs: GET /api/runner's field list; when the view withholds start and resume. Comment on how earlierCommits differs from hasRunnerCommit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review round 3 on #105: 503 before any start/resume refusal at shutdown; refusals point only to an action that runs (#91)

- During shutdown, start and resume answer 503 before any refusal, so nothing is recorded under the action ID (test: a body that finishes arriving after shutdown began).
- A run still finishing between items is refused as such, not as an active attempt.
- Start no longer points a fully run task to resume; resume names the status of a task that is neither running, queued nor in review.
- Docs: owed work is acted on before admission refuses a spent budget; the full refusal list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review round 4 on #105: test the view between items; refusal messages name an action that runs (#91)

- Test: while a run is still finishing between items, the view offers neither action and resume is refused.
- "Still finishing" says try again, not start; resume points to start only where start's checks pass; start names the task's status for a run revision.
- Docs: resume is no longer a later part. Test title matches what the 503 test checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Code review round 5 on #105: wording and docs (#91)

- start states a pending requeue as the reason when nothing ran at this revision.
- Docs list start and resume among user actions; the in-flight comment covers begin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the three Copilot findings deferred from #102 (#91)

- issueText refuses a title and body already over the prompt budget
  before reading any collaborator or comment page.
- Recovery warnings (recoveryWarnings) quote Docker labels and file
  names, so a newline or control character cannot forge a line of output.
- A lock.verify failure after startup prints its message and exits 1,
  not a stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address Copilot review on #105: serializer pre-check, terminal quoting (#91)

- issueText runs the prompt's own serializer on the title and body before
  reading any collaborator or comment page, so text that only exceeds the
  budget once escaped is refused just as early.
- quoteForTerminal (lifecycle.ts) escapes what JSON quoting leaves literal
  and a terminal still acts on: C1 controls, U+2028/U+2029, and invisible
  format and bidi characters. Recovery warnings use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants