From a793db686b590b700a6d699be86301d17ffe78b9 Mon Sep 17 00:00:00 2001 From: tyoon10 Date: Wed, 26 Aug 2026 22:11:47 +0000 Subject: [PATCH 1/5] fix(rescue): forbid node-based prompt writes and pgrep wait loops --- plugins/codex/agents/codex-rescue.md | 5 +++++ plugins/codex/skills/codex-cli-runtime/SKILL.md | 6 +++++- tests/commands.test.mjs | 13 +++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/codex/agents/codex-rescue.md b/plugins/codex/agents/codex-rescue.md index 7009ec86a..7597a1e1d 100644 --- a/plugins/codex/agents/codex-rescue.md +++ b/plugins/codex/agents/codex-rescue.md @@ -41,6 +41,11 @@ Forwarding rules: - Return the stdout of the `codex-companion` command exactly as-is. - If the Bash call fails or Codex cannot be invoked, return nothing. +Prompt assembly and background handling: + +- Pass the task prompt inline as positional arguments to `task`. Do not write prompt files to disk using `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. +- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. If the task is run with `--background`, or if the Bash harness moves the call to the background after the timeout, return the printed job ID and the suggested `/codex:status ` command exactly as output and stop. + Response style: - Do not add commentary before or after the forwarded `codex-companion` output. diff --git a/plugins/codex/skills/codex-cli-runtime/SKILL.md b/plugins/codex/skills/codex-cli-runtime/SKILL.md index 0e91bfb50..8c91e93fb 100644 --- a/plugins/codex/skills/codex-cli-runtime/SKILL.md +++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md @@ -21,7 +21,7 @@ Execution rules: - Leave `--effort` unset unless the user explicitly requests a specific effort. - Leave model unset by default. Add `--model` only when the user explicitly asks for one. - Map `spark` to `--model gpt-5.3-codex-spark`. -- Default to a write-capable Codex run by adding `--write` unless the user explicitly asks for read-only behavior or only wants review, diagnosis, or research without edits. +- Default to a write-capable Codex run in `codex:codex-rescue` unless the user explicitly asks for read-only behavior. Command selection: - Use exactly one `task` invocation per rescue handoff. @@ -35,6 +35,10 @@ Command selection: - `--effort`: accepted values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. - `task --resume-last`: internal helper for "keep going", "resume", "apply the top fix", or "dig deeper" after a previous rescue run. +Prompt and background handling: +- Pass the prompt inline as positional arguments to `task`. Do not write prompt files to disk with `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. +- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. If `task` is invoked with `--background`, or if the Bash harness moves the call to the background, return the job ID and suggested `/codex:status ` command exactly as output and stop. + Safety rules: - Default to write-capable Codex work in `codex:codex-rescue` unless the user explicitly asks for read-only behavior. - Preserve the user's task text as-is apart from stripping routing flags. diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index c34b06059..e5eecfad2 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -187,6 +187,19 @@ test("transfer, result, and cancel commands are exposed as deterministic runtime assert.match(resultHandling, /if Codex was never successfully invoked, do not generate a substitute answer at all/i); }); +test("rescue agent forbids node-based prompt writes and pgrep background wait loops", () => { + const agent = read("agents/codex-rescue.md"); + const runtimeSkill = read("skills/codex-cli-runtime/SKILL.md"); + + assert.match(agent, /Do not write prompt files to disk/i); + assert.match(agent, /node[^\n]*fs/i); + assert.match(agent, /Do not poll.*pgrep/i); + assert.match(agent, /return the printed job ID/i); + assert.match(runtimeSkill, /Do not write prompt files to disk/i); + assert.match(runtimeSkill, /Do not poll.*pgrep/i); + assert.match(runtimeSkill, /return the job ID and suggested/i); +}); + test("internal docs use task terminology for rescue runs", () => { const runtimeSkill = read("skills/codex-cli-runtime/SKILL.md"); const promptingSkill = read("skills/gpt-5-4-prompting/SKILL.md"); From 93e7e8d486ec064e83c8004c1d8f1c1c615ad543 Mon Sep 17 00:00:00 2001 From: tyoon10 Date: Wed, 26 Aug 2026 22:14:19 +0000 Subject: [PATCH 2/5] fix(rescue): preserve read-only exemptions in codex-cli-runtime skill --- plugins/codex/skills/codex-cli-runtime/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/codex/skills/codex-cli-runtime/SKILL.md b/plugins/codex/skills/codex-cli-runtime/SKILL.md index 8c91e93fb..207c7b743 100644 --- a/plugins/codex/skills/codex-cli-runtime/SKILL.md +++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md @@ -21,7 +21,7 @@ Execution rules: - Leave `--effort` unset unless the user explicitly requests a specific effort. - Leave model unset by default. Add `--model` only when the user explicitly asks for one. - Map `spark` to `--model gpt-5.3-codex-spark`. -- Default to a write-capable Codex run in `codex:codex-rescue` unless the user explicitly asks for read-only behavior. +- Default to a write-capable Codex run by adding `--write` unless the user explicitly asks for read-only behavior or only wants review, diagnosis, or research without edits. Command selection: - Use exactly one `task` invocation per rescue handoff. @@ -40,7 +40,7 @@ Prompt and background handling: - Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. If `task` is invoked with `--background`, or if the Bash harness moves the call to the background, return the job ID and suggested `/codex:status ` command exactly as output and stop. Safety rules: -- Default to write-capable Codex work in `codex:codex-rescue` unless the user explicitly asks for read-only behavior. +- Default to write-capable Codex work in `codex:codex-rescue` unless the user explicitly asks for read-only behavior or only wants review, diagnosis, or research without edits. - Preserve the user's task text as-is apart from stripping routing flags. - Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own. - Return the stdout of the `task` command exactly as-is. From f9862c5db785732e7f560c9ceba95dd32bf10b96 Mon Sep 17 00:00:00 2001 From: tyoon10 Date: Thu, 27 Aug 2026 00:21:04 +0000 Subject: [PATCH 3/5] fix(rescue): require -- prompt delimiter and drop harness job ID promise --- plugins/codex/agents/codex-rescue.md | 6 ++++-- plugins/codex/skills/codex-cli-runtime/SKILL.md | 8 +++++--- tests/commands.test.mjs | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/codex/agents/codex-rescue.md b/plugins/codex/agents/codex-rescue.md index 7597a1e1d..c3e9d843e 100644 --- a/plugins/codex/agents/codex-rescue.md +++ b/plugins/codex/agents/codex-rescue.md @@ -43,8 +43,10 @@ Forwarding rules: Prompt assembly and background handling: -- Pass the task prompt inline as positional arguments to `task`. Do not write prompt files to disk using `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. -- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. If the task is run with `--background`, or if the Bash harness moves the call to the background after the timeout, return the printed job ID and the suggested `/codex:status ` command exactly as output and stop. +- Pass the task prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ""`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Do not write prompt files to disk using `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. +- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. When the call uses `--background`, return the printed job ID and the suggested `/codex:status ` command exactly as output and stop. +- A foreground call that the Bash harness moves to the background after its timeout never prints a companion job ID, and the harness identifier is not one. Return whatever the harness printed as-is; do not invent a job ID or suggest a `/codex:status` command for it. Response style: diff --git a/plugins/codex/skills/codex-cli-runtime/SKILL.md b/plugins/codex/skills/codex-cli-runtime/SKILL.md index 207c7b743..6e3fded4b 100644 --- a/plugins/codex/skills/codex-cli-runtime/SKILL.md +++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md @@ -9,7 +9,7 @@ user-invocable: false Use this skill only inside the `codex:codex-rescue` subagent. Primary helper: -- `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task ""` +- `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task [runtime options] -- ""` Execution rules: - The rescue subagent is a forwarder, not an orchestrator. Its only job is to invoke `task` once and return that stdout unchanged. @@ -36,8 +36,10 @@ Command selection: - `task --resume-last`: internal helper for "keep going", "resume", "apply the top fix", or "dig deeper" after a previous rescue run. Prompt and background handling: -- Pass the prompt inline as positional arguments to `task`. Do not write prompt files to disk with `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. -- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. If `task` is invoked with `--background`, or if the Bash harness moves the call to the background, return the job ID and suggested `/codex:status ` command exactly as output and stop. +- Pass the prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ""`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Do not write prompt files to disk with `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. +- Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. When `task` is invoked with `--background`, return the job ID and suggested `/codex:status ` command exactly as output and stop. +- A foreground call that the Bash harness moves to the background never prints a companion job ID, and the harness identifier is not one. Return whatever the harness printed as-is; do not invent a job ID or suggest a `/codex:status` command for it. Safety rules: - Default to write-capable Codex work in `codex:codex-rescue` unless the user explicitly asks for read-only behavior or only wants review, diagnosis, or research without edits. diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index e5eecfad2..d5e3f4ff0 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -187,7 +187,7 @@ test("transfer, result, and cancel commands are exposed as deterministic runtime assert.match(resultHandling, /if Codex was never successfully invoked, do not generate a substitute answer at all/i); }); -test("rescue agent forbids node-based prompt writes and pgrep background wait loops", () => { +test("rescue agent forbids unsafe prompt writes, wait loops, and harness job-ID assumptions", () => { const agent = read("agents/codex-rescue.md"); const runtimeSkill = read("skills/codex-cli-runtime/SKILL.md"); @@ -195,9 +195,13 @@ test("rescue agent forbids node-based prompt writes and pgrep background wait lo assert.match(agent, /node[^\n]*fs/i); assert.match(agent, /Do not poll.*pgrep/i); assert.match(agent, /return the printed job ID/i); + assert.match(agent, /task \[runtime options\] -- ""/); + assert.match(agent, /do not invent a job ID or suggest a `\/codex:status` command for it/i); assert.match(runtimeSkill, /Do not write prompt files to disk/i); assert.match(runtimeSkill, /Do not poll.*pgrep/i); assert.match(runtimeSkill, /return the job ID and suggested/i); + assert.match(runtimeSkill, /task \[runtime options\] -- ""/); + assert.match(runtimeSkill, /do not invent a job ID or suggest a `\/codex:status` command for it/i); }); test("internal docs use task terminology for rescue runs", () => { From 22fc0b4ba5f8f5752e309196ce5956411adbddad Mon Sep 17 00:00:00 2001 From: tyoon10 Date: Thu, 27 Aug 2026 00:22:05 +0000 Subject: [PATCH 4/5] fix(rescue): keep primary helper line unchanged in runtime skill --- plugins/codex/skills/codex-cli-runtime/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/codex/skills/codex-cli-runtime/SKILL.md b/plugins/codex/skills/codex-cli-runtime/SKILL.md index 6e3fded4b..314aaeb78 100644 --- a/plugins/codex/skills/codex-cli-runtime/SKILL.md +++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md @@ -9,7 +9,7 @@ user-invocable: false Use this skill only inside the `codex:codex-rescue` subagent. Primary helper: -- `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task [runtime options] -- ""` +- `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task ""` Execution rules: - The rescue subagent is a forwarder, not an orchestrator. Its only job is to invoke `task` once and return that stdout unchanged. From 36ff14ac5ab37a1014b43e90e252d4e5b7beeb54 Mon Sep 17 00:00:00 2001 From: tyoon10 Date: Sat, 29 Aug 2026 21:37:33 +0000 Subject: [PATCH 5/5] fix(rescue): require single-quote shell escaping for inline prompts --- plugins/codex/agents/codex-rescue.md | 3 ++- plugins/codex/skills/codex-cli-runtime/SKILL.md | 3 ++- tests/commands.test.mjs | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/codex/agents/codex-rescue.md b/plugins/codex/agents/codex-rescue.md index c3e9d843e..153aa855a 100644 --- a/plugins/codex/agents/codex-rescue.md +++ b/plugins/codex/agents/codex-rescue.md @@ -43,7 +43,8 @@ Forwarding rules: Prompt assembly and background handling: -- Pass the task prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ""`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Pass the task prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ''`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Single-quote the prompt so Bash performs no expansion, and escape every embedded single quote as `'\''` (so `don't stop` is passed as `'don'\''t stop'`). Never wrap the prompt in double quotes: `$(...)`, backticks, `$VAR`, and a bare `"` would be expanded or would terminate the argument locally before the companion receives the text. - Do not write prompt files to disk using `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. - Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. When the call uses `--background`, return the printed job ID and the suggested `/codex:status ` command exactly as output and stop. - A foreground call that the Bash harness moves to the background after its timeout never prints a companion job ID, and the harness identifier is not one. Return whatever the harness printed as-is; do not invent a job ID or suggest a `/codex:status` command for it. diff --git a/plugins/codex/skills/codex-cli-runtime/SKILL.md b/plugins/codex/skills/codex-cli-runtime/SKILL.md index 314aaeb78..ad7874c9b 100644 --- a/plugins/codex/skills/codex-cli-runtime/SKILL.md +++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md @@ -36,7 +36,8 @@ Command selection: - `task --resume-last`: internal helper for "keep going", "resume", "apply the top fix", or "dig deeper" after a previous rescue run. Prompt and background handling: -- Pass the prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ""`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Pass the prompt inline, as the last arguments and after a `--` delimiter: `task [runtime options] -- ''`. Without `--`, option-like text inside the prompt such as `--write` is parsed as a runtime flag and stripped from the prompt. +- Single-quote the prompt so Bash performs no expansion, and escape every embedded single quote as `'\''` (so `don't stop` is passed as `'don'\''t stop'`). Never wrap the prompt in double quotes: `$(...)`, backticks, `$VAR`, and a bare `"` would be expanded or would terminate the argument locally before the companion receives the text. - Do not write prompt files to disk with `node`, `fs`, shell heredocs, or any other interpreter in order to consume them with `--prompt-file`. - Do not poll, `pgrep`, `watch`, `tail` logs, or run wait loops for a background task. When `task` is invoked with `--background`, return the job ID and suggested `/codex:status ` command exactly as output and stop. - A foreground call that the Bash harness moves to the background never prints a companion job ID, and the harness identifier is not one. Return whatever the harness printed as-is; do not invent a job ID or suggest a `/codex:status` command for it. diff --git a/tests/commands.test.mjs b/tests/commands.test.mjs index d5e3f4ff0..c47d29423 100644 --- a/tests/commands.test.mjs +++ b/tests/commands.test.mjs @@ -195,12 +195,14 @@ test("rescue agent forbids unsafe prompt writes, wait loops, and harness job-ID assert.match(agent, /node[^\n]*fs/i); assert.match(agent, /Do not poll.*pgrep/i); assert.match(agent, /return the printed job ID/i); - assert.match(agent, /task \[runtime options\] -- ""/); + assert.match(agent, /task \[runtime options\] -- ''/); + assert.match(agent, /escape every embedded single quote as `'\\''`.*Never wrap the prompt in double quotes/i); assert.match(agent, /do not invent a job ID or suggest a `\/codex:status` command for it/i); assert.match(runtimeSkill, /Do not write prompt files to disk/i); assert.match(runtimeSkill, /Do not poll.*pgrep/i); assert.match(runtimeSkill, /return the job ID and suggested/i); - assert.match(runtimeSkill, /task \[runtime options\] -- ""/); + assert.match(runtimeSkill, /task \[runtime options\] -- ''/); + assert.match(runtimeSkill, /escape every embedded single quote as `'\\''`.*Never wrap the prompt in double quotes/i); assert.match(runtimeSkill, /do not invent a job ID or suggest a `\/codex:status` command for it/i); });