From 2dd4f59b67ded19aa286db66be4526ac2714f7b1 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 18 Aug 2026 10:38:55 +0900 Subject: [PATCH 1/3] docs(devlog): adopt close-on-dev-merge for this run only User decision: close issues once the fix is on dev rather than waiting for a stable release, scoped to this campaign. Recording what it does and does not change. It removes the released-in gate; it does not lower the evidence bar. A close still requires the fix demonstrably on origin/dev by ancestry, and an umbrella still does not close from a partial fix. The issues that stay open under the new policy stay open for reasons that were never about release timing - #1852 needs the async work that is still unmerged, #1926 has two halves still live in bridge.ts, #1866 was explicitly scoped out of #1900, and #1730 is a different provider and round from what #1884 fixed. --- .../090_wave6_closeout.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md b/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md index 0c8c825344..b4ffa32309 100644 --- a/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md +++ b/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md @@ -41,3 +41,26 @@ resolved: close-on-dev vs close-on-release; #1059 consecutive-green count; Cursor non-loopback HTTP; Antigravity undocumented protocol posture; needs-info lifetime; upstream-tracker accounting; #1795 recovery shape; #1899 disposition; #1836 disposition; #1903 HTTP/1.1 default. +## Closure policy for THIS run (user decision, 2026-08-18) + +> "이슈는 dev 머지되면 일단 닫아놔 이번 런만 그런거야" + +Close issues when the fix reaches `dev`, not when it reaches a stable release. **Scoped to this +run only** — the standing preference remains close-on-release, so a future campaign should not +read this as precedent. + +What this changes: the `released-in:vX.Y.Z` step no longer gates closure. What it does *not* +change is the evidence bar — a close still needs the fix demonstrably on `origin/dev` by +ancestry, and still must not close an umbrella from a partial fix. The three policy holds keep +their own reasons, which are about missing evidence rather than about release timing: + +| Issue | Still open because | +|-------|--------------------| +| #1059 | needs hosted Windows shard evidence; no local batch substitutes | +| #1795 | needs a live SenseNova/Kimi canary showing zero undeclared calls | +| #1852 | the reported defect (sync enumeration blocking the event loop) is #1876's unmerged async work | +| #1849 | umbrella; its root cause is #1942 and unstarted | +| #1049 | assessed and unstarted; needs the publication protocol | +| #1926 | destination scope landed, but credential scope and emit-before-commit are still live in `src/bridge.ts` | +| #1866 | explicitly scoped out of #1900; no PR addresses it | +| #1730 | different provider and round from #1884's ClinePass replay fix | From d0d04035a94fc5e27cb5803c9826bb8be81e1c8e Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 18 Aug 2026 10:43:41 +0900 Subject: [PATCH 2/3] docs(devlog): record the two held PRs in the closeout, not only the issues The still-open table covered issues and omitted #1891 and #1889, so a reader working from the closeout alone would find no trace of two deliberate holds - one of which is the campaign's most consequential finding. Both are blocked before merge, so the close-on-dev-merge decision does not reach them: that policy governs when a merged fix closes its issue, and neither has merged. --- .../260817_wave5_execution/090_wave6_closeout.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md b/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md index b4ffa32309..bf87a1a790 100644 --- a/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md +++ b/devlog/_plan/260817_wave5_execution/090_wave6_closeout.md @@ -64,3 +64,15 @@ their own reasons, which are about missing evidence rather than about release ti | #1926 | destination scope landed, but credential scope and emit-before-commit are still live in `src/bridge.ts` | | #1866 | explicitly scoped out of #1900; no PR addresses it | | #1730 | different provider and round from #1884's ClinePass replay fix | + +Two **pull requests** are also held, and they belong in this record even though the table +above is about issues — a reader working only from this document would otherwise see no trace +of them: + +| PR | Held because | +|----|--------------| +| #1891 | it makes `GOOGLE_ANTIGRAVITY_USER_AGENT` steerable into the `onboardUser` request body, violating this wave's accept criterion. Needs #1889 first, which is the one-line fix that makes `ide_version` a real constant. Detail in `080`. | +| #1889 | unsponsored `src/oauth/` surface, plus still draft. The `maintainer-sponsored` label is the record that a security review happened, so an agent applying it would falsify that record. | + +Neither is affected by the close-on-dev-merge decision: both are blocked *before* merge, so the +policy that governs when a merged fix closes its issue never reaches them. From 5764fd994cf3f340b71bea6e75b09e1e6deae0d9 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 18 Aug 2026 10:45:02 +0900 Subject: [PATCH 3/3] fix(adapters): restore the un-namespaced requirement on the shell-bridge check My port of the shell-bridge predicate dropped the !tool.namespace guard that the Cursor original carries, so the function name asserted a bareness check the body did not perform. The consequence is reachable. An MCP server can advertise its own exec_command or shell_command - docker, k8s and ssh servers plausibly do - and those arrive namespaced. They are not Codex's shell bridge, but they were cancelling code mode, so a genuine code-mode turn sitting beside an MCP shell tool silently lost its guidance and fell back to the generic parent-tool sentence. It failed safe rather than emitting false guidance, which is why nothing caught it. The same requirement now applies to the code-mode exec predicate. A namespaced freeform exec is some MCP server's tool, not Codex's unified exec. That case is unreachable through today's parser, which only sets freeform on custom-type tools that never carry a namespace, but the type does not exclude it and the symmetry is the point. Driven red: removing the guard fails the namespaced-MCP test. --- src/adapters/tool-catalog-nudge.ts | 21 +++++++++++++++++---- tests/tool-catalog-nudge.test.ts | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/adapters/tool-catalog-nudge.ts b/src/adapters/tool-catalog-nudge.ts index 913ce12584..9325125691 100644 --- a/src/adapters/tool-catalog-nudge.ts +++ b/src/adapters/tool-catalog-nudge.ts @@ -28,12 +28,25 @@ const NEIGHBOR_AGENT_TOOL_NAMES = ["Read", "Grep", "Glob", "Bash", "LS"] as cons const CODEX_UNIFIED_EXEC_TOOL_NAME = "exec"; const CODEX_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const; -function isCodexCodeModeExecTool(tool: Pick): boolean { - return tool.name === CODEX_UNIFIED_EXEC_TOOL_NAME && tool.freeform === true; +function isCodexCodeModeExecTool(tool: Pick): boolean { + return !tool.namespace && tool.name === CODEX_UNIFIED_EXEC_TOOL_NAME && tool.freeform === true; } -function isBareShellBridgeTool(tool: Pick): boolean { - return (CODEX_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).includes(tool.name); +/** + * BARE means un-namespaced, and the word is load-bearing. + * + * An MCP server can advertise its own `exec_command` or `shell_command` — a docker, k8s or ssh + * server plausibly does — and those arrive namespaced (`mcp__docker__exec_command`). They are + * not Codex's shell bridge, so they must not cancel code mode: a genuine code-mode turn that + * merely happens to sit beside an MCP shell tool would lose its guidance and fall back to the + * generic sentence. + * + * The Cursor original this was ported from (`isBareCodexShellBridgeTool`) carries the same + * `!tool.namespace` requirement; dropping it here made the name assert a check the body did not + * perform. + */ +function isBareShellBridgeTool(tool: Pick): boolean { + return !tool.namespace && (CODEX_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).includes(tool.name); } function quoteNames(names: readonly string[]): string { diff --git a/tests/tool-catalog-nudge.test.ts b/tests/tool-catalog-nudge.test.ts index 91d4ec7b54..f764f001fa 100644 --- a/tests/tool-catalog-nudge.test.ts +++ b/tests/tool-catalog-nudge.test.ts @@ -133,6 +133,30 @@ describe("non-OpenAI tool catalog nudge", () => { expect(note).toContain("`custom_exec` is Codex code mode"); }); + // "Bare" means un-namespaced. An MCP server can advertise its own `exec_command` — docker, + // k8s and ssh servers plausibly do — and that is not Codex's shell bridge. Letting it cancel + // code mode silently strips the guidance from a genuine code-mode turn, which is how the + // Cursor original (`isBareCodexShellBridgeTool`) has always read it. + test("a namespaced MCP shell tool does not cancel code mode", () => { + for (const name of ["exec_command", "shell_command"]) { + const note = buildNonOpenAIToolCatalogNudgeForTools([ + codeModeExec(), + { namespace: "mcp__docker", name, parameters: {} } as OcxTool, + ]); + + expect(note).toContain("is Codex code mode"); + } + }); + + test("a namespaced freeform exec is not Codex's own code-mode tool", () => { + const note = buildNonOpenAIToolCatalogNudgeForTools([ + { namespace: "mcp__sandbox", name: "exec", freeform: true, parameters: {} } as OcxTool, + ]); + + expect(note).not.toContain("is Codex code mode"); + expect(note).toContain("call the listed parent tool and use those helpers only inside that tool's input"); + }); + // `advertised` holds WIRE names. A provider that rewrites them (Claude OAuth `custom_`, // Anthropic compat `cx_`) must not have every neighbor name declared unavailable while the // catalog plainly lists the prefixed form.