diff --git a/.changes/harness-ops/headless-d2-permission-pins-fdba65a7.md b/.changes/harness-ops/headless-d2-permission-pins-fdba65a7.md new file mode 100644 index 0000000000..fc5cb659c2 --- /dev/null +++ b/.changes/harness-ops/headless-d2-permission-pins-fdba65a7.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Fixed + +- The `consume-restarts print-schedule` command prints `--permission-mode auto`, plus `--permission-prompts none` when the CLI is 2.1.259 or later, on every scheduled `claude -p` line, so the reader no longer inherits the user's `defaultMode`. Regenerate an existing schedule to pick this up. diff --git a/.github/actions/resolve-config/callers.test.mjs b/.github/actions/resolve-config/callers.test.mjs index 57cca294b1..1fe6bba76c 100644 --- a/.github/actions/resolve-config/callers.test.mjs +++ b/.github/actions/resolve-config/callers.test.mjs @@ -1602,19 +1602,21 @@ const PERMISSION_PROMPTS_NONE_LAUNCHERS = new Set([ "d02b8697e092705153f0996549b05f95a446bb52", ]); -test("pr-run-activity-read.yml: every run-claude-cli pin is a launcher checked to pass --permission-prompts none", () => { - const pins = []; - for (const [id, job] of Object.entries(workflow("pr-run-activity-read.yml").jobs ?? {})) { - for (const step of job.steps ?? []) { - const uses = String(step.uses ?? ""); - if (!uses.includes("/run-claude-cli@")) continue; - const sha = uses.slice(uses.indexOf("@") + 1); - pins.push(sha); - assert.match(sha, /^[0-9a-f]{40}$/, `${id} ${step.id} pins a full SHA`); - assert.ok(PERMISSION_PROMPTS_NONE_LAUNCHERS.has(sha), `${id} ${step.id}: ${sha} is not checked to pass --permission-prompts none`); +test("every run-claude-cli step pin in this repo's workflows is a launcher checked to pass --permission-prompts none", () => { + const files = new Set(); + for (const file of workflowFiles()) { + for (const [id, job] of Object.entries(workflow(file).jobs ?? {})) { + for (const step of job.steps ?? []) { + const uses = String(step.uses ?? ""); + if (!uses.includes("/run-claude-cli@")) continue; + const sha = uses.slice(uses.indexOf("@") + 1); + files.add(file); + assert.match(sha, /^[0-9a-f]{40}$/, `${file} ${id} ${step.id} pins a full SHA`); + assert.ok(PERMISSION_PROMPTS_NONE_LAUNCHERS.has(sha), `${file} ${id} ${step.id}: ${sha} is not checked to pass --permission-prompts none`); + } } } - assert.ok(pins.length >= 2, pins.join(", ")); + for (const file of ["pr-run-activity-read.yml", "pr-verify.yml", "pr-unblock.yml"]) assert.ok(files.has(file), [...files].join(", ")); }); // Each launcher leg is followed by a check that the CLI's init message diff --git a/plugins/harness-ops/skills/lanes/context/restart-consumer.md b/plugins/harness-ops/skills/lanes/context/restart-consumer.md index 4734f20ab1..ff0a2189d9 100644 --- a/plugins/harness-ops/skills/lanes/context/restart-consumer.md +++ b/plugins/harness-ops/skills/lanes/context/restart-consumer.md @@ -133,7 +133,11 @@ from cmd.exe. Two scheduled forms, different trade-offs: -- **Headless skill form**: `claude -p "/harness-ops:lanes consume-restarts run"`. +- **Headless skill form**: `claude -p "/harness-ops:lanes consume-restarts run" + --permission-mode auto --permission-prompts none`. The mode is stated, never + inherited from user settings; `--permission-prompts none` is printed only when + the CLI that runs `print-schedule` accepts it, so regenerate the schedule after + upgrading from an older CLI. Survives plugin updates (the skill resolves `${CLAUDE_PLUGIN_ROOT}` freshly at each invocation, so the task never embeds a plugin cache path that rots), but each tick is a paid model turn. Bound the spend: `claude` supports `--model` diff --git a/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh b/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh index 80100a8f9d..7b48489c71 100755 --- a/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh +++ b/plugins/harness-ops/skills/lanes/scripts/restart-consumer.sh @@ -217,6 +217,9 @@ CONSUMER_MARKER="harness-ops:restart-consumer" CONSUMER_LANE="restart-consumer" SENTINEL_PREFIX="