Repository navigation
feat(harness-ops): let control-claude-code run unattended on sessions it launches #11386
Description
Parent
Refs #8651. Evidence-verification plan: Brief and acceptance criteria at #11364 (comment); Design and Plan comments are on #11364.
Depends on
- none
Outcome
control-claude-code runs unattended on sessions it launches (no longer attended-only), and the spike results for the OAuth and API-key legs are recorded.
Demo: run the driver tests; the new cases pass and grep -c "Attended runs only" prints 0.
Plan (phase S4a)
Satisfies: AC2
Size Medium. Depends on: none (spike first). Plugin: harness-ops. Design: T12.
- Spike (first work item, local or WSL Ubuntu 24.04 only; no CI leg in this slice): Ready with
CLAUDE_CODE_OAUTH_TOKENset and withANTHROPIC_API_KEYset; recordapiKeySourceand the first screen (key-approval prompt or not). Write results into the PR body. SKILL.md: removeAttended runs only.(plugins/harness-ops/skills/control-claude-code/SKILL.md:8), state unattended runs on sessions it launches, keep no sign-in and no Desktop; link the Brief as superseding app-control: new plugin plus testing and debugging changes #7581's 2026-10-23 criterion.config/defaults.json:maxCaptures: 20.CLAUDE_CODE_HIDE_CWD=1at launch.- Sanity Check:
node --test plugins/harness-ops/skills/control-claude-code/driver/driver.test.mjsexits 0 with new cases (attended still requiresnone; unattended run of a session the driver launches works);grep -c "Attended runs only" plugins/harness-ops/skills/control-claude-code/SKILL.md= 0;bash plugins/harness-ops/skills/control-claude-code/driver/driver.test.shexits 0 on WSL Ubuntu 24.04, with theapiKeySourceper credential recorded in the PR body.
Design excerpt
- harness-ops / control-claude-code (owns Claude Code knowledge): drops
Attended runs only.,
adds## Mask,credentials.ci,maxCaptures, masked rendering plus text sidecars in the driver,
Ready accepting the named CI credential's key source, plugin dir from changed files.
This slice carries only the unattended-run part of the harness-ops bullet: dropping Attended runs only., maxCaptures and the launch environment. The credentials.ci key and Ready accepting a named CI credential's key source are not in it (Q10 below).
Acceptance criteria
Brief criteria this slice satisfies:
- AC2: The same chain (confirm → run-e2e → drive) runs attended locally and unattended in CI.
Done when:
-
node --test plugins/harness-ops/skills/control-claude-code/driver/driver.test.mjsexits 0 with new cases (attended still requiresnone; unattended run of a session the driver launches works);grep -c "Attended runs only" plugins/harness-ops/skills/control-claude-code/SKILL.md= 0;bash plugins/harness-ops/skills/control-claude-code/driver/driver.test.shexits 0 on WSL Ubuntu 24.04, with theapiKeySourceper credential recorded in the PR body. -
SKILL.mdstates unattended runs on sessions it launches, keeps no sign-in and no Desktop, and links the Brief as superseding app-control: new plugin plus testing and debugging changes #7581's 2026-10-23 criterion - The spike's
apiKeySourceper credential and first-screen result are in the PR body - The plugin's version and CHANGELOG are bumped per the repository's release convention
-
ci-statusis green on the PR head
Owner decision Q10 (2026-10-30)
Q10 answered by the owner: "we already have pr-verify". The CI capture job uses pr-verify's existing credential legs (PR_VERIFY_CLAUDE_API_KEY, then PR_VERIFY_CLAUDE_CODE_OAUTH_TOKEN on handoff), one credential per job; no dedicated key. In scope here: the driver accepts the one credential its job provides under --unattended (credentials.ci), Ready accepts that key source, and two credentials set at once is refused. The S7 isolation probe (#8651) still runs before capture is enabled in CI.
Out of scope
- The CI capture job and its credential (the pr-verify capture job, S7 = feat(pr-pipeline): run the project's app-control skill in pr-verify after the deterministic tests #8651).
🤖 Generated with Claude Code
Activity
github-actions commentedon Oct 30, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Knowable
Flags: none
Blocked by: none
Understood: this slice lets control-claude-code run unattended on sessions the driver launches. It removes "Attended runs only." from SKILL.md, sets maxCaptures to 20, and sets CLAUDE_CODE_HIDE_CWD=1 at launch. It also adds driver test cases, records the OAuth and API-key spike results (apiKeySource and first screen) in the PR body, and bumps the version and CHANGELOG. The issue gives exact done-when checks. It explicitly leaves the CI credential (Q10) out of scope. The WSL run and the spike are host work that a person or host run has to do. The related "mask captures" issue in the open list covers a different slice, so I don't see a duplicate.
Automated triage, applied by the lanes App.
github-actions commentedon Oct 30, 2026
melodic-automation-lanes commentedon Oct 31, 2026
This records a re-fire of an earlier refusal, not a new refusal. The lanes kill switch stopped run 41146752888, the work-resolve-issue run that
agent-readystarted, before it did any work; that run ended at 2026-10-30T23:54:00Z. work-unblock has now removed and addedagent-readyback to start a fresh run. A kill-switch refusal is no fault in this issue or the lanes, so it never counts against later re-fires; after limits.limit-stops-per-issue of them since a person last addedagent-readyor a run last reported, work-unblock waits longer before each one, up to 24 hours.github-actions commentedon Oct 31, 2026
2 remaining items
kyle-sexton commentedon Nov 1, 2026
Route:
host-run. Written by the intake triage session, not the owner.The lane run stopped with reason
security, but the cause was not a security finding. It was theissue-editedstop (.github/scripts/work-resolve-issue/stop-routes.json:9). This account edited the body at 2026-10-30T23:56:27Z, afteragent-readywas added at 23:49:45Z, and the runner refuses text changed after that label (.github/scripts/work-resolve-issue/write-issue.mjs:247-250). work-unblock's re-fire anchors back to the same label (write-issue.mjs:132-143), so no automatic retry clears it.Re-adding
agent-readywould not finish the issue either. Done-when items 1 and 3 need host results: item 1 runsdriver.test.shon WSL Ubuntu 24.04 and records each credential'sapiKeySource, and item 3 records the spike'sapiKeySourceand first screen in the PR body. The issue lane's Bash runs only commands its allow rules name, and none namesclaude(.github/actions/collect-base-activity/action.yml:490-494). A host session runs the spike and makes the code change in the same pull request. The criteria are unchanged.kyle-sexton commentedon Nov 2, 2026
Host run for the Backlog HITL session has claimed this issue and opened #12257 (Refs, not Closes: the spike's two credential legs remain).
docs(harness-ops): say what a run does at maxCaptures and fix the REA…
kyle-sexton commentedon Nov 2, 2026
Host run. Built the unattended slice of control-claude-code and ran the spike's subscription-login leg on WSL; the two credential legs could not run on this host.
What ran (Claude Code 2.1.316, WSL, Ubuntu 26.04):
host-run/11386-control-claude-code-unattended:SKILL.mddropsAttended runs only.and states unattended runs on the session it launches (no sign-in, no attach, no Desktop), linking the Brief as superseding app-control: new plugin plus testing and debugging changes #7581's 2026-10-23 criterion; the launch forcesCLAUDE_CODE_HIDE_CWD=1;config/defaults.jsongainsmaxCaptures: 20; aminorharness-ops fragment under.changes/.bash plugins/harness-ops/skills/control-claude-code/driver/driver.test.sh: 39 pass, 0 fail, including the new cases (unattended run of a launched session: no terminal, parent key stripped, cwd hidden, input taken, stopped; Ready refuses every non-nonekey source).grep -c "Attended runs only" .../SKILL.md: 0.scripts/check-affected.sh --testson the committed head: 14 passed, 2 skipped.driver.mjs readyreportedapiKeySource: noneand listedharness-ops(exit 0).launchthen a screenshot: the first screen goes straight to the input prompt, with no key-approval prompt and no working directory shown.Against the done-when list:
driver.test.shpassed on WSL Ubuntu 26.04, not 24.04; CI's Ubuntu runner covers the suite on the PR.apiKeySourceper credential and first screen: subscription leg recorded; theCLAUDE_CODE_OAUTH_TOKENandANTHROPIC_API_KEYlegs were not run..changes/harness-opsfragment (ADR 0048; the release PR bumps the version).ci-statusgreen: met on head15305e2129. Both review lanes reviewed that head with 0 findings; round 1's two claude-review findings (maxCapturesunenforced, stale README row) were fixed in15305e2129, and the inline thread is resolved.For the owner: the
CLAUDE_CODE_OAUTH_TOKENleg needs a host session with that variable already set in its environment (a token fromclaude setup-token, which this run may not create); theANTHROPIC_API_KEYleg needs a session with an API key already set. Note that the driver unsetsANTHROPIC_API_KEYbefore launch, so that leg's Ready result is expected to benoneunless the key also reaches Claude Code another way.Scope note: the issue body says both that
credentials.ciand Ready accepting a CI key source are out of this slice and, in its Q10 section, "In scope here". The done-when list names neither, so this PR leaves them out; they are listed under the PR's## Remaining on #11386.Process note: the claim comment was posted after the build, with the PR, because the write gate held behind another waiter.
PR: #12257 (
Refs: #11386, opened ready through/source-control:pull-request create; not armed, not merged).Target labels: add none; remove none.
kyle-sexton commentedon Nov 2, 2026
Decision recorded. (agent; not an owner decision)
Done-when 3's
ANTHROPIC_API_KEYspike leg is dropped.ANTHROPIC_API_KEYbefore it launches a session, so Ready reportsapiKeySource: noneon that leg by construction. A live run would only show that the unset works, anddriver.test.shalready pins that ("parent key stripped", 39/39 on the feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257 head).CLAUDE_CODE_OAUTH_TOKENleg is still open. It needs a token fromclaude setup-token, which agents do not create, so it is offered to the owner as an optional step.credentials.ci/ Ready CI key-source scope listed under feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257's## Remaining on #11386.kyle-sexton commentedon Nov 2, 2026
Claim. Backlog HITL host-run worker is taking this now: the
credentials.ci/ Ready CI key-source part, plus Ubuntu 24.04 evidence fordriver.test.sh.Decision recorded. (agent; not an owner decision)
credentials.ci, Ready accepting the one CI credential's key source under--unattended, and refusing two credentials set at once are in this issue's scope.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, per feat(harness-ops): verify PR acceptance criteria by driving Claude Code (screenshots, video) before merge #11364's superseding Q11 record (feat(harness-ops): verify PR acceptance criteria by driving Claude Code (screenshots, video) before merge #11364 (comment)).ANTHROPIC_API_KEY, print mode reportsapiKeySource: ANTHROPIC_API_KEY(https://code.claude.com/docs/en/env-vars#variables: "In non-interactive mode (-p), the key is always used when present"), and the interactive TUI asks once to approve the key. That prompt is an input for the CI capture job (feat(pr-pipeline): run the project's app-control skill in pr-verify after the deterministic tests #8651), not for this slice.fix(harness-ops): correct control-claude-code's credential claims and…
docs(harness-ops): say what unsetting the holder's credential does an…
kyle-sexton commentedon Nov 2, 2026
Host run. Built the
credentials.cipart of control-claude-code's unattended mode: the one CI credential, its key source in Ready, and a refusal when two are set. Only the owner'sCLAUDE_CODE_OAUTH_TOKENspike leg is left on this issue.What ran (Claude Code 2.1.316, WSL):
credentials.ciin this slice, and no sibling issue owns it.host-run/11386-ci-credential:config/defaults.json:credentials.cinamesANTHROPIC_API_KEYandCLAUDE_CODE_OAUTH_TOKEN.driver.mjs:launch,readyandlogtake--unattended. That keeps the one CI variable set, setsCLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1, and has the session holder drop the key onceclaudestarts. Ready andlogaccept that credential's source. Two set at once exits 1 before anything starts. Attended behavior is unchanged./proc/<pid>/environofclaudeor of the session holder (unsetting it in the holder does not change that view), so feat(pr-pipeline): run the project's app-control skill in pr-verify after the deterministic tests #8651's probe should read both, so the scrub does not prove the separation feat(harness-ops): verify PR acceptance criteria by driving Claude Code (screenshots, video) before merge #11364's Q11 record requires. The docs now say so. No CI job passes--unattendeduntil feat(pr-pipeline): run the project's app-control skill in pr-verify after the deterministic tests #8651's S7 isolation probe proves the key is out of reach; if it is not, the Q11 record sends the question back to the owner.SKILL.md, README row, aminorchanges fragment.driver.test.sh: 46 pass, 0 fail, on the committed head and again after merging main.scripts/check-affected.sh --tests: 14 passed, 2 skipped.Against the done-when list:
test-bashjob runs onubuntu-24.04(.github/workflows/pr-require-checks.yml) and passed. UNVERIFIED: that its test selection includeddriver.test.sh; the job log was not read.ANTHROPIC_API_KEY, and the interactive session asks once to approve the key, which is input for feat(pr-pipeline): run the project's app-control skill in pr-verify after the deterministic tests #8651. TheCLAUDE_CODE_OAUTH_TOKENleg is the owner's step. Runclaude setup-token, setCLAUDE_CODE_OAUTH_TOKEN, then runnode plugins/harness-ops/skills/control-claude-code/driver/driver.mjs ready --unattended --plugin-dir <dir> --out <file>. IfapiKeySourceis notnone(the UNVERIFIED value the driver expects now), updateCI_KEY_SOURCESindriver.mjs. This run did not create, read or set any token.ci-status: green on head9a87349b9f. Both review lanes reviewed that head with 0 findings. Their earlier 5 threads (Codex and claude-review on SKILL.md restating upstream behavior; the attended OAuth claim; the credential reach; the holder's/procenviron) are answered and resolved.PR: #12372 (
Refs: #11386, opened through/source-control:pull-request createand flipped ready; not armed, not merged).Only the owner's
CLAUDE_CODE_OAUTH_TOKENspike leg remains on #11386.Target labels: add none; remove none.
kyle-sexton commentedon Nov 2, 2026
Status: what landed and what is left.
The issue-triage session checked the five done-when items, the Q10 scope the 2026-11-02 decision puts here, and the API-key leg drop, against main at 0ed6022.
Landed:
grep -c "Attended runs only" plugins/harness-ops/skills/control-claude-code/SKILL.mdprints 0 (feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257).SKILL.mdstates attended or unattended runs on the session it launches (:8-9), links the Brief as superseding app-control: new plugin plus testing and debugging changes #7581's 2026-10-23 criterion (:10-11), and never signs in or drives Claude Desktop (:13-14). feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257.CLAUDE_CODE_HIDE_CWD=1(driver/driver.mjs:139) andconfig/defaults.json:4setsmaxCaptures: 20(feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257).none(driver/driver.test.mjs:142,:697) and an unattended run drives the session it launched (:494).driver.test.shpassed on WSL Ubuntu 26.04: 39 in feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257, 46 in feat(harness-ops): accept the one CI credential in unattended control-claude-code runs #12372.credentials.ci(config/defaults.json:13), Ready accepting that credential's key source under--unattended(driver/driver.mjs:118, testdriver.test.mjs:809), and a refusal when two are set (driver.test.mjs:170). feat(harness-ops): accept the one CI credential in unattended control-claude-code runs #12372.apiKeySource: none, first screen straight to the input prompt) is in feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257's body; theANTHROPIC_API_KEYleg was dropped by the decision above..changes/harness-ops/(the release PR bumps the version), andci-statusgreen on feat(harness-ops): let control-claude-code run unattended on sessions it launches #12257 and feat(harness-ops): accept the one CI credential in unattended control-claude-code runs #12372.Left:
CLAUDE_CODE_OAUTH_TOKENleg:apiKeySourceand first screen recorded. It needs a token fromclaude setup-token, which agents do not create. If the source is notnone,CI_KEY_SOURCESindriver.mjschanges to match.bash plugins/harness-ops/skills/control-claude-code/driver/driver.test.shexits 0 on WSL Ubuntu 24.04. Not shown yet: both host runs were on 26.04. feat(harness-ops): accept the one CI credential in unattended control-claude-code runs #12372'stest-bash (0)job passed on a GitHub-hostedubuntu-24.04runner (.github/workflows/pr-require-checks.yml:3322), not WSL, and whether its selection includeddriver.test.shis unverified. That job's log, or a WSL 24.04 run, settles it.kyle-sexton commentedon Nov 2, 2026
Owner run: OAuth token leg. The owner ran
claude setup-token, then the control-claude-code driver'sreadywith onlyCLAUDE_CODE_OAUTH_TOKENset (--unattended,--plugin-dir plugins/harness-ops), on Claude Code 2.1.316, 2026-11-02:apiKeySource: noneSo a claude.ai subscription token reports
none, the same as the signed-in login, andreadyaccepts it. This settles the UNVERIFIED note atplugins/harness-ops/skills/control-claude-code/driver/driver.mjs:108-116: the comment can drop its UNVERIFIED and cite this record. The stream file held no token text (checked).