Skip to content

Run Ask in the lane D agent container - #54

Open
mchwang wants to merge 2 commits into
docs/reconcile-plan-with-codefrom
ask-in-container
Open

mchwang wants to merge 2 commits into
docs/reconcile-plan-with-codefrom
ask-in-container

Conversation

@mchwang

@mchwang mchwang commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #52 (base docs/reconcile-plan-with-code). When #52 merges, GitHub retargets this PR to main.

Summary

Ask no longer runs the claude or codex CLI on the host. It runs in lane D's container, in the read-only questions phase. This closes the interim R1 exception that #52 recorded.

Before After
Where the agent runs Host process, temporary folder, your environment Lane D container: read-only root, no capabilities, bounded storage
Code it can see Only the context in the prompt That context, plus a read-only clone of the reviewed snapshot head at /work
Commands Turned off by CLI flags questions phase: read, list and search only, enforced by lane D's policy
Network Host network Vendor-only proxy
Sign-in Host CLI login or keychain CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token) or Codex auth.json, read from the environment and never stored

How it works

  • runner/question-container.ts builds the image once per worker, clones the head, allocates storage, captures the invocation (no approved argv), and starts the adapter. Storage is removed only after settled, as the D5 handoff requires.
  • runner/question-worker.ts: every lane D entry point is synchronous (Docker and Git calls), so a worker thread owns them and the review server stays responsive. Lane D's trust registries live in that one worker.
  • runner/question-agent.ts (QuestionWorker): a question settles only when the worker reports that the container and storage are gone, so a cancelled or timed-out container can't outlive the state that started it.
  • There is no host fallback. A missing token, a missing auth.json or no Docker fails with a message that names what's missing. Provider failures now include the vendor's short reason, for example 401 Invalid bearer token.

Behavior changes for users

  • Ask, including npm run demo, needs Docker plus a token or auth.json. The first question builds the agent image. If that takes longer than the two-minute question deadline, the question fails; Docker keeps the finished layers, and a retry continues from them.
  • A repository larger than the 512 MiB tmpfs allocation fails closed.

Tests

  • test/question-agent.test.ts, orchestration with injected lane D dependencies:
    • the invocation uses the questions phase with no argv and the reviewed head;
    • the token goes only to the adapter;
    • the input folder is read-only and contains only schema.json;
    • storage is released after settlement, never before;
    • each cancel reason maps to the right stop reason;
    • missing credentials fail before any Docker or Git work;
    • the image is built once;
    • storage is released when setup fails after allocation.
  • The worker bridge runs on a real Worker with a stub: answer passthrough, cancellation that waits for the worker's reply, and crash recovery.
  • test/questions.test.ts: the agent receives the configured repository and snapshot head. The timeout-then-retry regression required by AGENTS.md is already covered by "keeps cancelled invocations tracked until they settle".
  • test/agent-question.test.ts (real Docker; added to the Agent isolation workflow and excluded from main CI like the other Docker suites):
    • with a fake token, the real worker gets through image build, clone, storage, network and container start, and receives Anthropic's 401. Only a request that left the container through the vendor proxy can get that response;
    • the live case (CODEBOOST_RUN_AUTH_PROBES=1) asks for a random word that exists only in a file in the repository, so a correct answer shows the agent read /work.

Validation

Evidence on the merged head (after merging the updated #52 branch, which includes main at 5e97aa3):

  • npm run typecheck: passed.
  • Unit tests with CI's excludes: 464/464 passed.
  • npm run test:browser: 58/58 passed.
  • test/agent-question.test.ts on local Docker (earlier head f5b2f09): passed, with the live case skipped. CI's real-docker job also passed.
  • Self-review of the full diff under AGENTS.md. The live Claude case has not run; it needs a real CLAUDE_CODE_OAUTH_TOKEN.

Ownership note

The plan assigns this move to lane F. F1 (#49, merged) was docs only. F1c (#57) also edits runner/questions.ts, so whichever of #54 and #57 lands second must be rebased, fitting Ask's worker shutdown (Questions.close → QuestionWorker.close) into F1c's shutdown order. This PR edits shared files owned by the integration owner (runner/questions.ts, web/public/app.js, .github/workflows/ci.yml, .github/workflows/agent-isolation.yml), so it needs that owner's review.

Review-lesson audit

No review findings yet.

🤖 Generated with Claude Code

Ask used to run the claude/codex CLI on the host with each CLI's own
restrictions, an interim exception to R1. It now uses lane D's invocation
boundary in the read-only "questions" phase: a clone of the reviewed
snapshot head at /work, no commands, vendor-only network, and no other
host files. There is no host fallback.

- runner/question-container.ts: build image, clone, allocate bounded
  storage, capture, start the Claude/Codex adapter; release storage only
  after the invocation settles. Deps are injectable for unit tests.
- runner/question-worker.ts: lane D setup is synchronous, so a worker
  thread owns it and the review server stays responsive.
- runner/question-agent.ts: QuestionWorker bridge; a question settles only
  when the worker reports the container and storage are gone.
- Credentials come from the environment only: CLAUDE_CODE_OAUTH_TOKEN for
  Claude, CODEBOOST_CODEX_AUTH_FILE or CODEX_HOME/auth.json for Codex.
- Provider failures include the vendor's short message (e.g. a 401).
- test/agent-question.test.ts runs the path on real Docker (Agent
  isolation workflow); its live case needs the auth-probe credentials.
- Plan, README, Settings copy and implementation docs updated; the R1
  exception is closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 26, 2026 08:20

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

Critical and moderate findings remain around invocation integrity, cleanup recovery, worker crashes, and CI coverage.

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

Open (3)
What changed in this PR

Moves Ask from host CLI execution into lane D’s isolated Docker container workflow.

Changes:

  • Adds worker-thread orchestration and container-backed Claude/Codex execution.
  • Adds credential, storage, cancellation, cleanup, and Docker integration tests.
  • Updates UI, CLI guidance, documentation, and CI workflows.

Unresolved findings concern invocation identity/context, resource cleanup and crash recovery, and workflow path coverage.

File Summary
web/​public/​app.js Updated settings guidance
web/​cli.ts Updated CLI guidance
test/​questions.test.ts Question lifecycle coverage
test/​question-agent.test.ts Orchestration and worker tests
test/​fixtures/​question-worker-stub.ts Worker test fixture
test/​agent-question.test.ts Real Docker integration tests
runner/​questions.ts Ask orchestration and scope
runner/​question-worker.ts Lane D worker bridge
runner/​question-container.ts Container lifecycle and cleanup
runner/​question-agent.ts Worker lifecycle and cancellation
README.md Containerized Ask documentation
docs/​implementation/​read-only-review.md Superseded host-adapter documentation
docs/​implementation/​agent-isolation.md Ask isolation documentation
docs/​designs/​codeboost-plan-indexed-review.md Updated roadmap and safety status
.github/​workflows/​ci.yml Excludes Docker tests from main CI
.github/​workflows/​agent-isolation.yml Runs Ask isolation tests

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

Comment on lines +59 to +60
export function answerFromResult(provider: Provider, result: InvocationResult): string {
if (result.stopReason) throw new Error(stopMessages[result.stopReason]);
Comment thread runner/question-agent.ts
Comment on lines +26 to +30
const fail = (error: Error) => {
if (this.worker !== worker) return;
this.worker = undefined;
for (const job of this.pending.values()) job.reject(new Error(`The agent container worker stopped: ${error.message}`));
this.pending.clear();
Comment on lines +108 to +112
const failures: unknown[] = [];
if (filesystems) try { deps.removeFilesystems(filesystems); } catch (error) { failures.push(error); }
try { chmodSync(input, 0o700); } catch { /* not created */ }
try { rmSync(root, { recursive: true, force: true }); } catch (error) { failures.push(error); }
if (failures.length) throw new AggregateError(failures, 'Question container cleanup did not settle.');
…nto ask-in-container

# Conflicts:
#	docs/designs/codeboost-plan-indexed-review.md
Copilot AI review requested due to automatic review settings September 26, 2026 09:44

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

Unresolved lifecycle, cleanup, cancellation, identity, permissions, result-validation, and CI coverage findings remain.

Review effort: Lite
Findings: 2 High severity · 3 Medium severity

Open (5)

Comment on lines +62 to +64
if (result.exitCode !== 0) {
const detail = result.stdout.replace(/\s+/g, ' ').trim().slice(0, 300);
throw new Error(`${name} could not answer. Check its sign-in and usage limits.${detail ? ` ${name} said: ${detail}` : ''}`);
const invocation = deps.capture({ clone, phase: 'questions', vendor: question.provider, approvedArgv: [],
deadline: question.deadline, attemptId: question.attemptId,
context: { snapshotId: question.snapshotId, planId: question.planId, planRevision: question.planRevision,
assignmentId: question.noteId, referencedCodeHash: createHash('sha256').update(question.prompt).digest('hex'),
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