Repository navigation
Description
Refs: #7580
Refs: #7555
Decision: #7555 (comment)
Blocked by: #8816 (adds the app-control plugin and /app-control:drive, whose report.json this step reads).
Agent Brief
Type: Feature
Summary: After the deterministic tests pass, the verifier runs the project's own app-control skill against the PR head and folds what it finds into its verdict. Stack-neutral: the project names its skill; Docker is used only if that skill uses it.
Problem: The verifier judges a PR from its diff and text; nothing drives the running app the way a user would. The line that asked for this ("agent browser checks run after the deterministic tests, with no write token while PR code runs") sat in Phase 9, which closed with it unbuilt.
Current behavior:
.github/workflows/pr-verify.yml:3-27: runs onworkflow_runafterpr-require-checkscompletes; "No pull request code runs here"; job tokens can only read (:66-68,:384-386).- No app-control plugin exists yet; Phase 13 creates
/app-control:driveand the generatedcontrol-<app>skill shape.
Desired behavior:
- A setting in
docs/conventions/pr-pipeline.yamlnames the project's app-control skill. Unset: the step is skipped and the verdict says no app check ran. - When set: a separate job, after the deterministic checks pass, checks out the PR head and runs
/app-control:drive --unattended, which finds the project's control skill; the verdict job judges drive'sreport.json(drive itself never judges; D54). It holds no write token and no secret beyond the model credential the run needs, on a fresh GitHub-hosted runner, under ADR 0049's hardening. - Its findings go to the verdict job as data, framed as untrusted (
docs/conventions/untrusted-content/README.md); a blocking finding makes the verdict FAIL. - Nothing in the workflow names a stack. Docker appears only if the project's skill starts it.
Key interfaces:
.github/workflows/pr-verify.yml;.github/scripts/pr-verify/write-verdict.mjs.docs/conventions/pr-pipeline.yaml(new key) anddocs/conventions/pr-pipeline/README.md("Verifier verdict")./app-control:drive'sreport.jsoncontract from feat(app-control): add the app-control plugin with /app-control:drive #8816 (plugins/app-control/skills/drive/reference/evidence.md).
Done when:
- The diff adds the setting, the new job and the verdict input, and the job's
permissions:grant no write scope (read from the diff). - With the setting unset, the verdict notes that no app check ran (a
workflow.test.mjscase). CI'stest-nodepasses on the PR (a PR check; this run does not run it). - CI's
check-pluginspasses on the PR (a PR check; this run does not run it). - Security & Cost's pass on the final head is recorded on the PR (
.github/workflowschanges; owner rule 2026-10-20).
Out of scope:
- Building app-control itself (app-control: new plugin plus testing and debugging changes #7581).
- A stack-specific default (Docker or any other).
Origin:
- Verifier agents that run the app and click around like a user, then fix and repeat: hers (Lauren, stream ~53:10), adapted.
- Docker app stack plus browser checks in CI: the owner's (2026-10-16); moved to Phase 13 and made stack-neutral: ours, confirmed by the owner 2026-10-20.
Review: security (runs PR code in CI).
Do not run with: other pr-verify.yml changes.
Size Large, Complexity High, Uncertainty Knowable.
Slice of #7580 (Phase 13), filed from the owner's 2026-10-20 disposition round.
Metadata
Metadata
Assignees
Labels
Type
Fields
Work status
Activity
kyle-sexton commentedon Oct 30, 2026
Phase text for S7 (the pr-verify capture job), from the evidence-verification plan (Brief: #11364 (comment)). Slices are sub-issues of this issue.
Satisfies: AC1, AC2, AC5, AC9, AC10
Size Large. Depends on: S2a (#11388), S4a (#11386), S4b (#11390), S6 (#11385), and owner answers to U1 and U2 (USER-RESERVED). Files:
.github/workflows/pr-verify.yml,.github/scripts/pr-verify/prompt.md,workflow.test.mjs. Design: T8, sequence flow 2.setup-runner-isolation, a head-code step tries to read the runtime token and the credential from a hook; the job fails closed if either is readable.capture: sameifas.github/workflows/pr-verify.yml:73-77(fork heads never run), gates, read-only token,.baseplugins,--trusted-root .base,/testing:run-e2e unattended app=<merge.verify-capture.app> base=<base-sha>; uploads publishable files aspr-capture-<pr>-<sha>withretention-days; skip with note when the key is unset..github/workflows/pr-verify.yml:102-175).timeout-minutes: 30; the outer session runs through ci-workflows'run-claude-cliwith its own role, a tool allowlist and the drivemaxEntryPointscap (S4b (feat(harness-ops): mask control-claude-code captures and launch changed plugins #11390)); a capture timeout leavescapture: blockedand never stops the judge.check-intentgainsneeds: capturewith!cancelled();check-intent-subscriptionandcheck-intent-fallbackkeep their success chain (.github/workflows/pr-verify.yml:434-445) and download the same run's artifact by id. All three download intopr-verify-data/capture/; the prompt frames it untrusted and says a missing or blocked capture is never a FAIL reason, while a trusted-text manifest lineEvidence loop: 3/3 cap reached, last NEEDS WORKis a FAIL (AC7's park path then runs).merge.verify-capture.app: claude-codeand show one in-repo PR with a judged capture (AC1). Comment block at.github/workflows/pr-verify.yml:33-36updated (reviewed change: jobs now take an artifact).node --test .github/scripts/pr-verify/workflow.test.mjsexits 0 with cases: capture job permissions contain nowrite; the three judge jobs carry identical capture-download steps; captureifincludes the head-repository check; no job-levelalways()in pr-verify.yml; only check-intentneeds: capture; a probe step exists, runs aftersetup-runner-isolationand before any head code, and has nocontinue-on-error;actionlintpasses.Depends on
🤖 Generated with Claude Code
https://claude.ai/code/session_01UJUn7KoThXoYQoxfvdCVi7
1 remaining item
kyle-sexton commentedon Oct 30, 2026
Owner decision 2026-10-30, Q11: the capture job stays in pr-verify as its own job (no write token; only its run directory reaches the judge). No verdict-forgery probe and no artifact-name filter: the only actors are the owner's own agents, and the producer and reviewer are separate agents. Q10: it uses pr-verify's existing credential legs. Remaining dependencies: #11388, #11386, #11390, #11385. Brief and decisions: #11364
kyle-sexton commentedon Oct 31, 2026
Decisions 2026-10-31 (audit of Q10-Q12, resolved from the owner's standing direction s1, s5, s10):
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1so hooks and Bash in the driven session get no credentials (https://code.claude.com/docs/en/env-vars). No separate OS user and no /proc defense: reading another process's environment on purpose needs a malicious actor, which the threat model excludes.kyle-sexton commentedon Nov 1, 2026
Decision recorded.
The capture job is built as the 2026-10-30 S7 phase text on this issue describes. S7 replaces two parts of the body: the job runs
/testing:run-e2e unattended app=<merge.verify-capture.app>, not/app-control:drive --unattended, and a missing or blocked capture is never a FAIL reason (the body said a blocking finding fails the verdict); the trusted manifest lineEvidence loop: 3/3 cap reached, last NEEDS WORKis a FAIL. Q10 is the owner's 2026-10-30 answer ("we already have pr-verify", #11364 issuecomment-6536099521): the job uses pr-verify's existing credential legs. Q11 was decided on 2026-10-31 from the owner's standing direction, not in the owner's words (#11364 issuecomment-6547731014, which supersedes issuecomment-6547464663): the capture job may hold that credential, and the PR's code runs in a child process whose environment and readable files hold no token. The 2026-10-31 comment here addsCLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, no process-group kill before upload, and evidence in workflow artifacts with a retention period; it drops a separate OS user and a /proc defense, not S7's probe step, so the probe step stays. S6 (#11385) already added themerge.verify-capturekey, withappunset recordingcapture: no-app. At origin/mainb9ae6bbaedthis edits the pr-verify workflow, which the lanes App cannot push (.github/actions/resolve-config/patch-rules.json:75-76). The body's Security & Cost criterion stays: no later decision on this issue removes it. Of the S7 dependencies, #11388 and #11385 are closed; #11386 and #11390 are open, so the build waits on them. Agents decided this on evidence under standing direction §1.Acceptance criteria:
merge.verify-capture.appunset, the capture job is skipped and the verdict's evidence recordscapture: no-app(a.github/scripts/pr-verify/workflow.test.mjscase).setup-runner-isolationand before any head code, has nocontinue-on-error, and fails the job if a hook can read the runtime token or the credential.ifkeeps fork heads out, and only check-intent gainsneeds: capture.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1.Evidence loop: 3/3 cap reached, last NEEDS WORKis a FAIL.node --test .github/scripts/pr-verify/workflow.test.mjsexits 0, and CI'stest-nodeandcheck-pluginspass on the PR.Route: builder (Automation Lanes).
fix(harness-ops): correct control-claude-code's credential claims and…
kyle-sexton commentedon Nov 6, 2026
Security & Cost pass on the capture, main
39a20d000f: PASS, conditional, with one MEDIUM finding being fixed. It was a fresh-context review of.github/workflows/pr-verify-capture.yml,.github/scripts/pr-verify/and ci-workflowsrun-claude-cli/lane.cjs, against the threat model in standing direction §3 and §10 (the owner's decisions are recorded on #11364).pr-verify-capture.yml:925-935) sets no route limit on/testing:run-e2e, and three of its routes run head code: the non-UI route (run-e2e/SKILL.md:73), own driving (:95-96) and a committed suite (:166). Only the env scrub protects the key there. Separately, UNVERIFIED: head code could write a user-scope proxy or CA setting that the managed settings do not pin. No route to the key was found.blocked, and the managed settings pinNODE_EXTRA_CA_CERTSand the proxy variables. No exposure today, because with the key out of credit every capture recordsblockedbefore head code runs..basecomposite actions that run later, and so read the job's read-onlyGITHUB_TOKEN.lane.cjs:726-776already binds_work/_actionsread-only. If GitHub did not enforcecache-mode: read, this would become cache poisoning (HIGH).cache-mode: readenforcement is being checked against the GitHub docs.evidence.capture, but nothing gates on it, and the judges read the capture as untrusted data.Checked and clean:
:105,:1242,find-capture.mjs:70-76).x-api-keyis replaced, a per-job token cap applies, and the key never reaches its log..gitis removed before the checkout post steps;persist-credentials: false.The box stays unticked until the finding-1 fix merges and
cache-mode: readis confirmed.kyle-sexton commentedon Nov 6, 2026
Done-when graded: all 13 boxes ticked (4 in the body, 9 in the 2026-11-01 decision), against main after PR 13770. Several rows are met by the design change the owner chose on #12232 (Q1): the capture runs in its own workflow,
pr-verify-capture.yml, and pr-verify's judges download its artifact. #12232's closing table records the same mapping.merge.verify-captureindocs/conventions/pr-pipeline.yaml; the capture workflow; theevidenceblock inwrite-verdict.mjs:86-158; test "the capture workflow and its jobs can write nothing"pr-verify-capture.yml:176-179;workflow.test.mjs:1494;write-verdict.test.mjs:102(no-app):853-897). The probe step itself keepscontinue-on-errorso a credit-stop handoff ends green (owner fallback decision on #12232).env -iallowlist;sibling-init.shprobes for every pr-verify credential name and fails closed (#12232 row 2)needs: captureneedsit. Fork heads::105and:1242, and testworkflow.test.mjs:1352.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1pr-capture-*retention 1-30 days;prompt.md:85-87and:128-131node --test workflow.test.mjsexits 0pr-verify-capture.yml:1123-1137). GitHub enforcescache-mode: read: "a job cannot restore or save caches beyond the mode it is granted" (workflow syntax).Still open: #11364, this issue's last open sub-issue. It waits on #13757's three PRs (13762, 13765, 13774) and on one live capture when
PR_VERIFY_CLAUDE_API_KEYhas credit. This issue closes after #11364 does (R233).melodic-automation-lanes commentedon Nov 7, 2026
Every sub-issue of this split is closed as completed by a listed human or a merge (#11364, #11384, #11385, #11386, #11387, #11388, #11389, #11390, #11391, #11392, #11393, #12232), so the lanes App closes it as completed.
kyle-sexton commentedon Nov 7, 2026
Closing record. All 13 done-when items are met: 4 in the body and 9 in issuecomment-6552343414. The S&C grade of 2026-11-06 found the pr-verify app-control run built.
This issue stayed open only so its child, #11364, could close first. #11364 is now closed: a UI-change PR was captured locally, judged against its acceptance criteria by a fresh verifier, and its redacted evidence posted (#13958). #7580 closes next.