From 6bdf7ab38cac2b499ead44131a1aefd4c2dc38d2 Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 08:37:30 +0530 Subject: [PATCH 1/9] test(runner): reproduce headless Grok writers losing shell commands Grok isolated-write lanes cancel every run_terminal_command at a permission prompt the headless CLI cannot show, and the receipt reports malformed-output with evidence truncated to the init event. Co-Authored-By: Claude Opus 5.5 --- .../scripts/runner/commands.test.ts | 19 ++++++++++-- .../scripts/runner/parse-output.test.ts | 22 ++++++++++++++ .../poteto-mode/scripts/runner/run.test.ts | 29 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts index ea697ff2..ceb8b17b 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts @@ -100,7 +100,7 @@ describe("invocationCommand", () => { "--sandbox", "read-only", "--tools", - "read_file,grep,list_dir,run_terminal_cmd", + "read_file,grep,list_dir,run_terminal_command", "--disallowed-tools", "Agent,search_tool,use_tool", "--output-format", @@ -125,13 +125,16 @@ describe("invocationCommand", () => { expect.arrayContaining([ "--permission-mode", "acceptEdits", + "--allow", + "Bash", "--sandbox", "workspace", "--tools", - "read_file,grep,list_dir,run_terminal_cmd,search_replace", + "read_file,grep,list_dir,run_terminal_command,search_replace", ]) ); expect(grok.args).not.toContain("--always-approve"); + expect(grok.args).not.toContain("bypassPermissions"); const claude = invocationCommand( options({ provider: "claude", model: "fable", mode: "isolated-write" }) @@ -146,6 +149,18 @@ describe("invocationCommand", () => { ); }); + it("pre-approves Grok shell commands only for writers", () => { + const reader = invocationCommand( + options({ provider: "grok", model: "grok-4.6", mode: "read-only" }) + ); + expect(reader.args).not.toContain("--allow"); + const writer = invocationCommand( + options({ provider: "grok", model: "grok-4.6", mode: "isolated-write" }) + ); + expect(writer.args.filter((arg) => arg === "--allow")).toHaveLength(1); + expect(writer.args[writer.args.indexOf("--allow") + 1]).toBe("Bash"); + }); + it("covers low, medium, and high for every external provider", () => { const cases = [ { diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.test.ts index b4ebc041..5e15ccbc 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.test.ts @@ -133,4 +133,26 @@ describe("parseProviderOutput", () => { ) ).toThrow("final agent message"); }); + + it("reports a Grok error result as a provider failure, not malformed output", () => { + const stdout = JSON.stringify({ + type: "result", + subtype: "error_during_execution", + is_error: true, + stop_reason: "end_turn", + session_id: "grok-session", + modelUsage: { "grok-4.6-build": {} }, + }); + let thrown: unknown; + try { + parseProviderOutput("grok", stdout, "", "grok-4.6"); + } catch (error) { + thrown = error; + } + expect(thrown).toMatchObject({ + status: "child-failed", + evidence: stdout, + metadata: { reportedModel: "grok-4.6-build", sessionId: "grok-session" }, + }); + }); }); diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts index 20743b52..d9752303 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts @@ -120,6 +120,10 @@ if (name === "claude") { console.log(JSON.stringify({type:"thread.started",thread_id:"o1"})); console.log(JSON.stringify({type:"item.completed",item:{type:"agent_message",text:"CODEX_OK"}})); console.log(JSON.stringify({type:"turn.completed",usage:{input_tokens:20,cached_input_tokens:5,output_tokens:3,reasoning_output_tokens:1}})); +} else if (process.env.FAKE_GROK_PERMISSION_CANCELLED === "1") { + console.log(JSON.stringify({type:"system",subtype:"init",session_id:"g2",tools:["run_terminal_command"],slash_commands:Array(800).fill("skill")})); + console.log(JSON.stringify({type:"user",message:{role:"user",content:[{type:"tool_result",tool_use_id:"t0",content:"x".repeat(5000),is_error:false},{type:"tool_result",tool_use_id:"t1",content:"[{\\"type\\":\\"content\\",\\"content\\":{\\"type\\":\\"text\\",\\"text\\":\\"User cancelled the execution for tool \`run_terminal_command\`\\"}}]",is_error:true}]},session_id:"g2"})); + console.log(JSON.stringify({type:"result",subtype:"error_during_execution",is_error:true,stop_reason:"cancelled",errors:["cancelled"],session_id:"g2",usage:{input_tokens:30,output_tokens:4},total_cost_usd:0.02,modelUsage:{[model + "-build"]:{}}})); } else { console.log(JSON.stringify({type:"assistant",message:{content:[{type:"text",text:"progress"}]}})); console.log(JSON.stringify({type:"result",subtype:"success",is_error:false,result:"GROK_OK",session_id:"g1",usage:{input_tokens:30,output_tokens:4,total_tokens:34},total_cost_usd:0.02,modelUsage:{[model + "-build"]:{}}})); @@ -241,6 +245,7 @@ beforeEach(() => { delete process.env.FAKE_GROK_TRANSIENT_UNAUTH_PATH; delete process.env.FAKE_GROK_PREFLIGHT_LOG_PATH; delete process.env.FAKE_GROK_MISSING_MODEL; + delete process.env.FAKE_GROK_PERMISSION_CANCELLED; delete process.env.FAKE_DESCENDANT_HOLDS_PIPES_MS; delete process.env.FAKE_DESCENDANT_PID_PATH; delete process.env.FAKE_SELF_SIGNAL; @@ -265,6 +270,7 @@ afterEach(() => { delete process.env.FAKE_GROK_TRANSIENT_UNAUTH_PATH; delete process.env.FAKE_GROK_PREFLIGHT_LOG_PATH; delete process.env.FAKE_GROK_MISSING_MODEL; + delete process.env.FAKE_GROK_PERMISSION_CANCELLED; delete process.env.FAKE_DESCENDANT_HOLDS_PIPES_MS; delete process.env.FAKE_DESCENDANT_PID_PATH; delete process.env.FAKE_SELF_SIGNAL; @@ -294,6 +300,29 @@ describe("runLane", () => { }); } + it("classifies a Grok permission cancellation with the cancellation as evidence", async () => { + process.env.FAKE_GROK_PERMISSION_CANCELLED = "1"; + const input = options("grok", "grok-permission-cancelled"); + const result = await runLane(input); + expect(result.exitCode).toBe(77); + expect(existsSync(input.outputPath)).toBe(false); + const recorded = receipt(input.receiptPath); + expect(recorded).toMatchObject({ + status: "permission-cancelled", + reportedModel: "grok-4.6-build", + modelVerified: true, + sessionId: "g2", + usage: { inputTokens: 30, outputTokens: 4 }, + error: { + message: "grok cancelled run_terminal_command at a permission prompt it cannot show headless", + }, + }); + expect(recorded.error?.evidence).toContain("User cancelled the execution for tool"); + expect(recorded.error?.evidence).toContain('"stop_reason":"cancelled"'); + expect(recorded.error?.evidence).not.toContain('"subtype":"init"'); + expect(recorded.error?.evidence).not.toContain('"tool_use_id":"t0"'); + }); + it("records Codex's exact argv without fabricating a reported model", async () => { const input = options("codex"); const result = await runLane(input); From b14c96de875f4776a9382d431c831948f6a9c49d Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 08:37:38 +0530 Subject: [PATCH 2/9] fix(runner): let headless Grok writers run shell commands Grok cancels any tool call that needs an approval prompt when it runs headless, so isolated-write lanes lost every run_terminal_command and ended the turn. Writers now pass --allow Bash, which pre-approves the shell tool while the workspace sandbox, deny rules, and hooks still apply. The tool list uses Grok's real name, run_terminal_command. A Grok turn that ends on a permission cancellation now records permission-cancelled with the cancelled tool result and terminal event as evidence. Other Grok error results record child-failed. Both keep the reported model, session, usage, and cost instead of malformed-output. Co-Authored-By: Claude Opus 5.5 --- .../references/provider-dispatch.md | 4 +- .../poteto-mode/scripts/runner/commands.ts | 7 +- .../scripts/runner/parse-output.ts | 64 ++++++++++++++++--- .../skills/poteto-mode/scripts/runner/run.ts | 48 +++++++++----- .../poteto-mode/scripts/runner/types.ts | 1 + 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 74ab90da..791e1a76 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -80,7 +80,7 @@ Start the background process, continue launching the other lanes, then drain the The runner and its preflight have no implicit timeout. Do not invent a duration from role, mode, or a convenient round number; real implementation lanes can run for 90 minutes or much longer. Pass `--timeout` only when the user, an external service deadline, or a measured task contract supplies a real bound. That value starts at wrapper entry, before module loading and argument parsing, and remains one absolute deadline across setup, preflight, model execution, and output capture. It is never a fresh allowance per child, and long waits are armed in runtime-safe chunks without shortening the supplied deadline. Otherwise supervise liveness through the retained background task/session handle and cancel manually only on evidence that the run is dead. Cancel through that retained handle so the runner receives SIGINT or SIGTERM, sends it to an active child when one remains, stops waiting on inherited output pipes, removes the empty output reservation, and writes a `cancelled` receipt. Preserve that receipt; a retry is a new attempt with new unique output and receipt paths. Unchanged running state is not a dropout, and Claude's ten-minute foreground ceiling is never a reason to terminate a healthy lane. -Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox and write-capable tool list. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. +Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and an `--allow Bash` rule. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, so the rule pre-approves shell commands. The sandbox, deny rules, and hooks still apply, and a command that reaches outside the workspace is still cancelled. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. Every concurrent external lane needs distinct prompt, output, and receipt paths. The launcher reserves output and receipt paths exclusively and refuses to overwrite them. @@ -95,6 +95,6 @@ Success requires all of these: The receipt also carries elapsed time, token usage when the CLI exposes it, and cost when available. Keep it with the arena or review artifacts so parent-harness comparisons are evidence-based. -Any missing CLI, failed login, unavailable model, explicit timeout, cancellation, catchable post-reservation launcher failure, non-zero child exit, malformed result, or model mismatch is a receipt-bearing dropout. Record it and apply the calling skill's existing dropout policy. A `cancelled` receipt proves that the runner received the signal; its `signal` field is non-null only when the runner sent that signal to a still-active direct CLI child, and remains null when cancellation only stopped a post-exit pipe drain. The provider CLI owns any processes it starts beneath that direct child; the receipt does not claim a process-tree kill. Do not delete or overwrite the receipt. Never substitute the parent model, retry another provider, or reinterpret an external descriptor as a native model slug. +Any missing CLI, failed login, unavailable model, explicit timeout, cancellation, catchable post-reservation launcher failure, non-zero child exit, malformed result, provider-reported failure, or model mismatch is a receipt-bearing dropout. A Grok turn that ends because a tool call hit an approval prompt records `permission-cancelled`, names the tool, and carries the cancelled tool result and terminal event as evidence. Any other Grok error result records `child-failed` with its terminal event. Record it and apply the calling skill's existing dropout policy. A `cancelled` receipt proves that the runner received the signal; its `signal` field is non-null only when the runner sent that signal to a still-active direct CLI child, and remains null when cancellation only stopped a post-exit pipe drain. The provider CLI owns any processes it starts beneath that direct child; the receipt does not claim a process-tree kill. Do not delete or overwrite the receipt. Never substitute the parent model, retry another provider, or reinterpret an external descriptor as a native model slug. Start native and external lanes in the same fan-out phase, then wait for all of them before judging. A judge must not read candidate paths while their owners are still writing. diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts index 5f2b10c6..21f5e4d0 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts @@ -51,10 +51,14 @@ function grokSandbox(mode: AccessMode): string { } function grokTools(mode: AccessMode): string { - const readonly = ["read_file", "grep", "list_dir", "run_terminal_cmd"]; + const readonly = ["read_file", "grep", "list_dir", "run_terminal_command"]; return [...readonly, ...(mode === "isolated-write" ? ["search_replace"] : [])].join(","); } +function grokPermissionRules(mode: AccessMode): readonly string[] { + return mode === "isolated-write" ? ["--allow", "Bash"] : []; +} + function permissionMode(mode: AccessMode): string { return mode === "read-only" ? "plan" : "acceptEdits"; } @@ -130,6 +134,7 @@ export function invocationCommand(options: RunnerOptions): CommandSpec { options.effort, "--permission-mode", permissionMode(options.mode), + ...grokPermissionRules(options.mode), "--sandbox", grokSandbox(options.mode), "--tools", diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.ts index 81ed53d4..72e5d8f3 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/parse-output.ts @@ -10,6 +10,19 @@ import { type JsonObject = Record; +export class ProviderReportedFailure extends Error { + constructor( + message: string, + readonly status: "permission-cancelled" | "child-failed", + readonly evidence: string, + readonly metadata: Omit + ) { + super(message); + } +} + +const GROK_PERMISSION_CANCELLED = /User cancelled the execution for tool `([^`]+)`/; + function object(value: unknown): JsonObject | null { return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) @@ -84,8 +97,21 @@ function parseClaude(stdout: string, requestedModel: string): ParsedOutput { }; } +function cancelledGrokToolResult(event: JsonObject): string | null { + const content = object(event.message)?.content; + if (!Array.isArray(content)) return null; + const block = content.find((item) => { + const toolResult = object(item); + return toolResult?.type === "tool_result" + && GROK_PERMISSION_CANCELLED.test(JSON.stringify(toolResult.content)); + }); + return block === undefined ? null : JSON.stringify(block); +} + function parseGrok(stdout: string, requestedModel: string): ParsedOutput { let result: JsonObject | null = null; + let resultLine = ""; + let cancelledToolResult: string | null = null; for (const line of stdout.split("\n")) { if (line.trim().length === 0) continue; let raw: unknown; @@ -95,23 +121,43 @@ function parseGrok(stdout: string, requestedModel: string): ParsedOutput { throw new Error("grok emitted a non-JSON event"); } const event = object(raw); - if (event?.type === "result") result = event; + if (event?.type === "result") { + result = event; + resultLine = line; + } + if (event?.type === "user") { + cancelledToolResult = cancelledGrokToolResult(event) ?? cancelledToolResult; + } } if (result === null) throw new Error("grok result did not contain a terminal event"); - if (result.is_error === true || result.subtype !== "success") { - throw new Error("grok reported an error result"); - } - const text = nullableString(result.result); - if (text === null) throw new Error("grok result did not contain final text"); - - return { - text, + const metadata = { reportedModel: modelFromUsage(result.modelUsage, "grok", requestedModel), sessionId: nullableString(result.session_id), usage: normalizedUsage(result.usage), costUsd: finiteNumber(result.total_cost_usd) ?? null, }; + if (result.is_error === true || result.subtype !== "success") { + if (result.stop_reason === "cancelled" && cancelledToolResult !== null) { + const tool = GROK_PERMISSION_CANCELLED.exec(cancelledToolResult)?.[1]; + throw new ProviderReportedFailure( + `grok cancelled ${tool} at a permission prompt it cannot show headless`, + "permission-cancelled", + `${cancelledToolResult}\n${resultLine}`, + metadata + ); + } + throw new ProviderReportedFailure( + "grok reported an error result", + "child-failed", + resultLine, + metadata + ); + } + const text = nullableString(result.result); + if (text === null) throw new Error("grok result did not contain final text"); + + return { text, ...metadata }; } function parseCodex(stdout: string): ParsedOutput { diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts index 054564a4..0c747284 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts @@ -11,7 +11,11 @@ import { import { dirname, resolve } from "node:path"; import { invocationCommand, preflightCommand, type CommandSpec } from "./commands.ts"; import { versionedClaudeAlias } from "./model-aliases.ts"; -import { parseProviderOutput, reportedModelMatches } from "./parse-output.ts"; +import { + parseProviderOutput, + ProviderReportedFailure, + reportedModelMatches, +} from "./parse-output.ts"; import type { Provider, ReceiptStatus, @@ -428,6 +432,7 @@ function statusExitCode(status: ReceiptStatus): number { case "child-failed": return 70; case "unauthenticated": + case "permission-cancelled": return 77; case "timed-out": return 124; @@ -828,20 +833,33 @@ async function executeLane( } catch (error) { const message = error instanceof Error ? error.message : String(error); removeIfExists(options.outputPath); - receipt = completeReceipt(options, { - ...base, - status: "malformed-output", - reportedModel: null, - modelVerified: false, - modelEvidence: null, - sessionId: null, - usage: null, - costUsd: null, - error: { - message, - evidence: evidence(`${result.stderr}\n${result.stdout}`), - }, - }); + if (error instanceof ProviderReportedFailure) { + const proof = modelProof(options.provider, options.model, error.metadata.reportedModel); + receipt = completeReceipt(options, { + ...base, + status: error.status, + ...proof, + sessionId: error.metadata.sessionId, + usage: error.metadata.usage, + costUsd: error.metadata.costUsd, + error: { message, evidence: evidence(error.evidence) }, + }); + } else { + receipt = completeReceipt(options, { + ...base, + status: "malformed-output", + reportedModel: null, + modelVerified: false, + modelEvidence: null, + sessionId: null, + usage: null, + costUsd: null, + error: { + message, + evidence: evidence(`${result.stderr}\n${result.stdout}`), + }, + }); + } } writeReceipt(options.receiptPath, receipt); diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/types.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/types.ts index 11c6dfb9..596eaaf9 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/types.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/types.ts @@ -29,6 +29,7 @@ export type ReceiptStatus = | "unavailable-model" | "timed-out" | "child-failed" + | "permission-cancelled" | "malformed-output"; export interface NormalizedUsage { From 2431509ba3c834206a6b09d182b9343d8afb1e72 Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 10:09:08 +0530 Subject: [PATCH 3/9] test(runner): pin the environment a Grok writer's shell inherits With shell commands pre-approved, a Grok writer can read and send anything in its environment. The test requires parent tokens and agent sockets to stay out of it while paths, locale, proxies, and Grok's own settings pass through. Co-Authored-By: Claude Opus 5.5 --- .../poteto-mode/scripts/runner/run.test.ts | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts index d9752303..9db569b8 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts @@ -945,21 +945,49 @@ describe("childEnvironment", () => { CLAUDE_CODE_CHILD_SESSION: "1", KEEP_ME: "yes", }; - expect(childEnvironment("claude", source)).toEqual({ + expect(childEnvironment("claude", "isolated-write", source)).toEqual({ PATH: "/bin", CLAUDECODE: "1", CLAUDE_CODE_CHILD_SESSION: "1", KEEP_ME: "yes", }); - expect(childEnvironment("codex", source)).toEqual({ + expect(childEnvironment("codex", "isolated-write", source)).toEqual({ PATH: "/bin", CODEX_THREAD_ID: "codex", CODEX_CI: "1", KEEP_ME: "yes", }); - expect(childEnvironment("grok", source)).toEqual({ + expect(childEnvironment("grok", "read-only", source)).toEqual({ PATH: "/bin", KEEP_ME: "yes", }); }); + + it("passes a Grok writer, whose shell is pre-approved, only an allowlisted environment", () => { + const source = { + PATH: "/bin", + HOME: "/home/dev", + TMPDIR: "/tmp/dev", + LANG: "en_US.UTF-8", + LC_ALL: "en_US.UTF-8", + HTTPS_PROXY: "http://proxy:8080", + GROK_HOME: "/home/dev/.grok", + XAI_API_KEY: "xai-key", + GH_TOKEN: "gh-secret", + AWS_SECRET_ACCESS_KEY: "aws-secret", + SSH_AUTH_SOCK: "/tmp/agent.sock", + CLAUDECODE: "1", + KEEP_ME: "yes", + }; + expect(childEnvironment("grok", "isolated-write", source)).toEqual({ + PATH: "/bin", + HOME: "/home/dev", + TMPDIR: "/tmp/dev", + LANG: "en_US.UTF-8", + LC_ALL: "en_US.UTF-8", + HTTPS_PROXY: "http://proxy:8080", + GROK_HOME: "/home/dev/.grok", + XAI_API_KEY: "xai-key", + }); + }); }); From ba50963f6e9276ac217ae801a6c28ab39c9dd75a Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 10:10:53 +0530 Subject: [PATCH 4/9] fix(runner): keep parent secrets out of a Grok writer's shell A Grok writer's shell is pre-approved, and Grok's workspace sandbox reads the whole host and, on macOS, never blocks child-process network. The runner passed it the parent's full environment, so a prompt-injected writer could send any token there to the network. Grok writers now get an allowlisted environment: paths, user, shell, locale, terminal, proxy and CA settings, and GROK_* and XAI_*. Other lanes keep the existing identity-only scrub. The dispatch reference now states the real trust boundary instead of claiming commands outside the workspace are cancelled. Co-Authored-By: Claude Opus 5.5 --- .../references/provider-dispatch.md | 2 +- .../skills/poteto-mode/scripts/runner/run.ts | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 791e1a76..88493657 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -80,7 +80,7 @@ Start the background process, continue launching the other lanes, then drain the The runner and its preflight have no implicit timeout. Do not invent a duration from role, mode, or a convenient round number; real implementation lanes can run for 90 minutes or much longer. Pass `--timeout` only when the user, an external service deadline, or a measured task contract supplies a real bound. That value starts at wrapper entry, before module loading and argument parsing, and remains one absolute deadline across setup, preflight, model execution, and output capture. It is never a fresh allowance per child, and long waits are armed in runtime-safe chunks without shortening the supplied deadline. Otherwise supervise liveness through the retained background task/session handle and cancel manually only on evidence that the run is dead. Cancel through that retained handle so the runner receives SIGINT or SIGTERM, sends it to an active child when one remains, stops waiting on inherited output pipes, removes the empty output reservation, and writes a `cancelled` receipt. Preserve that receipt; a retry is a new attempt with new unique output and receipt paths. Unchanged running state is not a dropout, and Claude's ten-minute foreground ceiling is never a reason to terminate a healthy lane. -Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and an `--allow Bash` rule. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, so the rule pre-approves shell commands. The sandbox, deny rules, and hooks still apply, and a command that reaches outside the workspace is still cancelled. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. +Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and an `--allow Bash` rule. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, so the rule pre-approves shell commands. Deny rules, hooks, and Grok's built-in sensitive-path checks still apply, and a command they gate is still cancelled. The `workspace` sandbox confines writes to the worktree, `~/.grok`, and temporary directories, but it reads the whole host and, on macOS, never blocks child-process network. The runner therefore gives a Grok writer only an allowlisted environment (paths, user, shell, locale, terminal, proxy and CA settings, and `GROK_*` and `XAI_*`), so parent tokens and agent sockets never reach its shell. Treat a Grok writer as able to read host files and reach the network, and route it only prompts and repositories you trust. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. Every concurrent external lane needs distinct prompt, output, and receipt paths. The launcher reserves output and receipt paths exclusively and refuses to overwrite them. diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts index 0c747284..8965441a 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/run.ts @@ -17,6 +17,7 @@ import { reportedModelMatches, } from "./parse-output.ts"; import type { + AccessMode, Provider, ReceiptStatus, RunnerOptions, @@ -133,10 +134,47 @@ const CLAUDE_IDENTITY = [ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS", ] as const; +const GROK_WRITER_ENV = new Set([ + "PATH", + "HOME", + "USER", + "LOGNAME", + "SHELL", + "TMPDIR", + "LANG", + "TERM", + "COLORTERM", + "TZ", + "HTTP_PROXY", + "HTTPS_PROXY", + "NO_PROXY", + "ALL_PROXY", + "http_proxy", + "https_proxy", + "no_proxy", + "all_proxy", + "SSL_CERT_FILE", + "SSL_CERT_DIR", + "NODE_EXTRA_CA_CERTS", +]); + +const GROK_WRITER_ENV_PREFIXES = ["LC_", "XDG_", "GROK_", "XAI_"] as const; + +function grokWriterInherits(key: string): boolean { + return GROK_WRITER_ENV.has(key) + || GROK_WRITER_ENV_PREFIXES.some((prefix) => key.startsWith(prefix)); +} + export function childEnvironment( provider: Provider, + mode: AccessMode, source: NodeJS.ProcessEnv = process.env ): NodeJS.ProcessEnv { + if (provider === "grok" && mode === "isolated-write") { + return Object.fromEntries( + Object.entries(source).filter(([key]) => grokWriterInherits(key)) + ); + } const result = { ...source }; const remove = provider === "claude" ? CODEX_IDENTITY @@ -539,7 +577,7 @@ async function executeLane( ): Promise { const startedAt = new Date(started).toISOString(); const prompt = readFileSync(options.promptPath, "utf8"); - const env = childEnvironment(options.provider); + const env = childEnvironment(options.provider, options.mode); const executable = Bun.which(invocation.command, { PATH: env.PATH, cwd: options.cwd, From c7b03bb5180340c0f1cb927e9f2754b181073c69 Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 12:47:32 +0530 Subject: [PATCH 5/9] test(runner): require Grok's valid terminal tool ID in --tools Grok 1.0.41 accepts run_terminal_cmd as the --tools ID and shows the tool to the model as run_terminal_command. Passing run_terminal_command makes Grok drop the whole allowlist, so every lane, read-only included, gets all 17 tools. Co-Authored-By: Claude Opus 5.5 --- .../pstack/skills/poteto-mode/scripts/runner/commands.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts index ceb8b17b..aa0124d5 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts @@ -100,7 +100,7 @@ describe("invocationCommand", () => { "--sandbox", "read-only", "--tools", - "read_file,grep,list_dir,run_terminal_command", + "read_file,grep,list_dir,run_terminal_cmd", "--disallowed-tools", "Agent,search_tool,use_tool", "--output-format", @@ -130,7 +130,7 @@ describe("invocationCommand", () => { "--sandbox", "workspace", "--tools", - "read_file,grep,list_dir,run_terminal_command,search_replace", + "read_file,grep,list_dir,run_terminal_cmd,search_replace", ]) ); expect(grok.args).not.toContain("--always-approve"); From 7140d3033acd508d69739d4ae99eaf10c5c2eae0 Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 12:48:27 +0530 Subject: [PATCH 6/9] fix(runner): restore Grok's valid terminal tool ID run_terminal_command is the name the model sees, not a valid --tools ID. Grok drops an allowlist that contains an unknown ID, so every Grok lane had all 17 tools, including write and search_replace in read-only lanes. With run_terminal_cmd a writer lane gets exactly its 5 tools and a reader its 4. Co-Authored-By: Claude Opus 5.5 --- plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts index 21f5e4d0..f7b4a319 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts @@ -51,7 +51,7 @@ function grokSandbox(mode: AccessMode): string { } function grokTools(mode: AccessMode): string { - const readonly = ["read_file", "grep", "list_dir", "run_terminal_command"]; + const readonly = ["read_file", "grep", "list_dir", "run_terminal_cmd"]; return [...readonly, ...(mode === "isolated-write" ? ["search_replace"] : [])].join(","); } From c6010983e4c06b3f1b2e6d2067ad37aca75e8016 Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 12:50:06 +0530 Subject: [PATCH 7/9] test(runner): reproduce headless Grok writers losing file edits Grok's headless `acceptEdits` mode does not pre-approve its file tools. An isolated-write lane's `write` and `search_replace` calls stop at an approval prompt that headless mode cancels, so the lane ends without changing a file. The writer allowlist also leaves out `write`, Grok's file-creation tool. Expect the isolated-write argv to list `write` and to pass `--allow Edit` after `--allow Bash`. The read-only argv pin is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../skills/poteto-mode/scripts/runner/commands.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts index aa0124d5..aa34c757 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.test.ts @@ -130,7 +130,7 @@ describe("invocationCommand", () => { "--sandbox", "workspace", "--tools", - "read_file,grep,list_dir,run_terminal_cmd,search_replace", + "read_file,grep,list_dir,run_terminal_cmd,search_replace,write", ]) ); expect(grok.args).not.toContain("--always-approve"); @@ -149,7 +149,7 @@ describe("invocationCommand", () => { ); }); - it("pre-approves Grok shell commands only for writers", () => { + it("pre-approves Grok shell commands and file edits only for writers", () => { const reader = invocationCommand( options({ provider: "grok", model: "grok-4.6", mode: "read-only" }) ); @@ -157,8 +157,10 @@ describe("invocationCommand", () => { const writer = invocationCommand( options({ provider: "grok", model: "grok-4.6", mode: "isolated-write" }) ); - expect(writer.args.filter((arg) => arg === "--allow")).toHaveLength(1); - expect(writer.args[writer.args.indexOf("--allow") + 1]).toBe("Bash"); + const rules = writer.args.flatMap((arg, index) => + arg === "--allow" ? [writer.args[index + 1]] : [] + ); + expect(rules).toEqual(["Bash", "Edit"]); }); it("covers low, medium, and high for every external provider", () => { From fe11cc13e6122384a9945d17bfced92402d98e3a Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 12:55:20 +0530 Subject: [PATCH 8/9] fix(runner): pre-approve file edits for headless Grok writers Headless Grok cancels any tool call that needs an approval prompt. The `acceptEdits` mode does not pre-approve Grok's file tools, so Grok cancelled every `write` and `search_replace` call in an isolated-write lane. The lane exited 77 with `permission-cancelled` and changed nothing. Pass `--allow Edit` after `--allow Bash` for isolated-write lanes. Grok's `Edit` rule covers both `search_replace` and `write`, and the `workspace` sandbox still refuses writes outside the worktree. Add `write` to the writer's `--tools` allowlist so the lane can call Grok's file-creation tool. Read-only lanes are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../pstack/skills/poteto-mode/references/provider-dispatch.md | 2 +- plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 88493657..334bf646 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -80,7 +80,7 @@ Start the background process, continue launching the other lanes, then drain the The runner and its preflight have no implicit timeout. Do not invent a duration from role, mode, or a convenient round number; real implementation lanes can run for 90 minutes or much longer. Pass `--timeout` only when the user, an external service deadline, or a measured task contract supplies a real bound. That value starts at wrapper entry, before module loading and argument parsing, and remains one absolute deadline across setup, preflight, model execution, and output capture. It is never a fresh allowance per child, and long waits are armed in runtime-safe chunks without shortening the supplied deadline. Otherwise supervise liveness through the retained background task/session handle and cancel manually only on evidence that the run is dead. Cancel through that retained handle so the runner receives SIGINT or SIGTERM, sends it to an active child when one remains, stops waiting on inherited output pipes, removes the empty output reservation, and writes a `cancelled` receipt. Preserve that receipt; a retry is a new attempt with new unique output and receipt paths. Unchanged running state is not a dropout, and Claude's ten-minute foreground ceiling is never a reason to terminate a healthy lane. -Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and an `--allow Bash` rule. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, so the rule pre-approves shell commands. Deny rules, hooks, and Grok's built-in sensitive-path checks still apply, and a command they gate is still cancelled. The `workspace` sandbox confines writes to the worktree, `~/.grok`, and temporary directories, but it reads the whole host and, on macOS, never blocks child-process network. The runner therefore gives a Grok writer only an allowlisted environment (paths, user, shell, locale, terminal, proxy and CA settings, and `GROK_*` and `XAI_*`), so parent tokens and agent sockets never reach its shell. Treat a Grok writer as able to read host files and reach the network, and route it only prompts and repositories you trust. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. +Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and `--allow Bash` and `--allow Edit` rules. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, and its `acceptEdits` mode does not pre-approve Grok's file tools. `--allow Bash` pre-approves shell commands, and `--allow Edit` pre-approves both file tools, `search_replace` and `write`. Deny rules, hooks, and Grok's built-in sensitive-path checks still apply, and a command they gate is still cancelled. The `workspace` sandbox confines writes to the worktree, `~/.grok`, and temporary directories, but it reads the whole host and, on macOS, never blocks child-process network. The runner therefore gives a Grok writer only an allowlisted environment (paths, user, shell, locale, terminal, proxy and CA settings, and `GROK_*` and `XAI_*`), so parent tokens and agent sockets never reach its shell. Treat a Grok writer as able to read host files and reach the network, and route it only prompts and repositories you trust. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. Every concurrent external lane needs distinct prompt, output, and receipt paths. The launcher reserves output and receipt paths exclusively and refuses to overwrite them. diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts index f7b4a319..cf2a0fc5 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/commands.ts @@ -52,11 +52,11 @@ function grokSandbox(mode: AccessMode): string { function grokTools(mode: AccessMode): string { const readonly = ["read_file", "grep", "list_dir", "run_terminal_cmd"]; - return [...readonly, ...(mode === "isolated-write" ? ["search_replace"] : [])].join(","); + return [...readonly, ...(mode === "isolated-write" ? ["search_replace", "write"] : [])].join(","); } function grokPermissionRules(mode: AccessMode): readonly string[] { - return mode === "isolated-write" ? ["--allow", "Bash"] : []; + return mode === "isolated-write" ? ["--allow", "Bash", "--allow", "Edit"] : []; } function permissionMode(mode: AccessMode): string { From c1fe76fd1fe0f2c5795a05c8bd325d0b6eeac1dd Mon Sep 17 00:00:00 2001 From: smashru Date: Wed, 23 Sep 2026 13:06:37 +0530 Subject: [PATCH 9/9] docs(dispatch): name the shell commands headless Grok still cancels On grok CLI 1.0.41, `--allow Bash` pre-approves plain commands such as `touch`, `mkdir`, `git add`, and `python3 -c`. Grok still prompts for a shell command that writes output into a file, such as `> out.txt` or `| tee out.txt`, even with `--allow Edit`. It also prompts for a command it cannot split into simple segments, such as `$(...)`, `${VAR:-default}`, or a subshell. Headless mode cancels each of those prompts. A heredoc or a multi-line `python3 -c` passes when it does not redirect into a file. Tell Grok writers to change files with their file tools and to run build and test steps as plain commands. Co-Authored-By: Claude Opus 5.5 --- .../pstack/skills/poteto-mode/references/provider-dispatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 334bf646..a2c63b1e 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -80,7 +80,7 @@ Start the background process, continue launching the other lanes, then drain the The runner and its preflight have no implicit timeout. Do not invent a duration from role, mode, or a convenient round number; real implementation lanes can run for 90 minutes or much longer. Pass `--timeout` only when the user, an external service deadline, or a measured task contract supplies a real bound. That value starts at wrapper entry, before module loading and argument parsing, and remains one absolute deadline across setup, preflight, model execution, and output capture. It is never a fresh allowance per child, and long waits are armed in runtime-safe chunks without shortening the supplied deadline. Otherwise supervise liveness through the retained background task/session handle and cancel manually only on evidence that the run is dead. Cancel through that retained handle so the runner receives SIGINT or SIGTERM, sends it to an active child when one remains, stops waiting on inherited output pipes, removes the empty output reservation, and writes a `cancelled` receipt. Preserve that receipt; a retry is a new attempt with new unique output and receipt paths. Unchanged running state is not a dropout, and Claude's ten-minute foreground ceiling is never a reason to terminate a healthy lane. -Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and `--allow Bash` and `--allow Edit` rules. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, and its `acceptEdits` mode does not pre-approve Grok's file tools. `--allow Bash` pre-approves shell commands, and `--allow Edit` pre-approves both file tools, `search_replace` and `write`. Deny rules, hooks, and Grok's built-in sensitive-path checks still apply, and a command they gate is still cancelled. The `workspace` sandbox confines writes to the worktree, `~/.grok`, and temporary directories, but it reads the whole host and, on macOS, never blocks child-process network. The runner therefore gives a Grok writer only an allowlisted environment (paths, user, shell, locale, terminal, proxy and CA settings, and `GROK_*` and `XAI_*`), so parent tokens and agent sockets never reach its shell. Treat a Grok writer as able to read host files and reach the network, and route it only prompts and repositories you trust. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. +Read-only mode maps to Claude plan mode with project-only settings and an explicit tool list, Codex's read-only sandbox, and Grok plan mode plus its `read-only` sandbox and read-oriented tool list. Grok's built-in read-only profile deliberately keeps its own state and system temporary directories writable, so point a read-only Grok lane at the actual checkout rather than a worktree under `/tmp`, `/var/tmp`, or the host's temporary directory. `isolated-write` maps to Claude `acceptEdits` with project-only settings, Codex `workspace-write`, and Grok `acceptEdits` plus its `workspace` sandbox, write-capable tool list, and `--allow Bash` and `--allow Edit` rules. Headless Grok cancels any tool call that would need an approval prompt and ends the turn, and its `acceptEdits` mode does not pre-approve Grok's file tools. `--allow Bash` pre-approves shell commands, and `--allow Edit` pre-approves both file tools, `search_replace` and `write`. Grok still prompts for a shell command that writes output into a file, such as `> out.txt` or `| tee out.txt`, and for a command it cannot split into simple segments, such as `$(...)`, `${VAR:-default}`, or a subshell. Headless mode cancels those commands, so tell a Grok writer to create and edit files with its file tools and to run build and test steps as plain commands. Deny rules, hooks, and Grok's built-in sensitive-path checks still apply, and a command they gate is still cancelled. The `workspace` sandbox confines writes to the worktree, `~/.grok`, and temporary directories, but it reads the whole host and, on macOS, never blocks child-process network. The runner therefore gives a Grok writer only an allowlisted environment (paths, user, shell, locale, terminal, proxy and CA settings, and `GROK_*` and `XAI_*`), so parent tokens and agent sockets never reach its shell. Treat a Grok writer as able to read host files and reach the network, and route it only prompts and repositories you trust. Give every writer only a dedicated worktree or output directory. Never route a writer into the primary checkout. Every concurrent external lane needs distinct prompt, output, and receipt paths. The launcher reserves output and receipt paths exclusively and refuses to overwrite them.