From f48d3a12bb311ac6a7f27ec118ab10c4ef3bbdc3 Mon Sep 17 00:00:00 2001
From: mrroll <58090032+mrroll@users.noreply.github.com>
Date: Wed, 11 Nov 2026 04:50:26 +0800
Subject: [PATCH 1/2] fix(server): agents can use the browser a skill or the
user names
The shared orchestration instructions tell every agent to use "preview
tools only" for browser work. The exception for a browser that an
invoked skill or a documented repository workflow names, added in
#16956, was removed in #24850. An agent then refused a skill that the
user had invoked. With Agent browser access off, the preview tools were
denied too, so the agent had no browser that it was allowed to use.
- Restore the exception in the orchestration text that is always
injected, so it holds for every provider, with browser access on or
off.
- Name the same three sources (the user, an invoked skill, a documented
repo workflow) in the separate browser instructions.
- Assert that the Codex context carries the exception when browser
tools are not available.
- Add the `invoked-skill-uses-separate-browser` eval case.
- Raise the agent context budget by 148 bytes for Claude and 214 bytes
for Codex.
Verified locally: the new assertion fails on the unfixed text and
passes with the fix, the budget test matches the measured bytes, and
`vp run check:changed` passes. The eval case was validated with a dry
run only. It was not run against a model, so it is not shown to fail on
the old text.
Claude Fable 5.2 with GPT-6.1 Sol and Claude Opus 5.5 subagents, in
T3 Code.
---
apps/server/src/provider/CodexDeveloperInstructions.test.ts | 5 ++++-
apps/server/src/provider/agentContextBudget.json | 4 ++--
evals/mcp-tool-use/cases/visual-preview.ndjson | 1 +
.../provider-core/src/server/orchestrationInstructions.ts | 4 ++--
4 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/apps/server/src/provider/CodexDeveloperInstructions.test.ts b/apps/server/src/provider/CodexDeveloperInstructions.test.ts
index 5f4a28e886b6a..21860598a8190 100644
--- a/apps/server/src/provider/CodexDeveloperInstructions.test.ts
+++ b/apps/server/src/provider/CodexDeveloperInstructions.test.ts
@@ -58,7 +58,9 @@ describe("Codex runtime context", () => {
describe("Codex orchestration context", () => {
it("routes subagents native first, delegate_task for the rest", () => {
- const orchestration = Object.entries(buildCodexAdditionalContext({ model: "gpt-6.1-sol" }))
+ const orchestration = Object.entries(
+ buildCodexAdditionalContext({ model: "gpt-6.1-sol" }, false),
+ )
.filter(([key]) => key.startsWith("t3_code_orchestration_"))
.map(([, entry]) => entry.value)
.join("\n");
@@ -73,6 +75,7 @@ describe("Codex orchestration context", () => {
orchestration.includes("Every new delegate review or re-check is a new `delegate_task`"),
);
NodeAssert.ok(orchestration.includes("never a `t3_thread_send`"));
+ NodeAssert.ok(orchestration.includes("invoked skill"));
NodeAssert.ok(!orchestration.includes("inheritedOptions"));
NodeAssert.ok(!orchestration.includes("Prefer native"));
// The ACP terminal fallback is ACP-only; Codex agents never need it.
diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json
index 4439c50940722..bd87b82d92d86 100644
--- a/apps/server/src/provider/agentContextBudget.json
+++ b/apps/server/src/provider/agentContextBudget.json
@@ -1,5 +1,5 @@
{
- "claudeSystemAppendBytes": 5597,
- "codexAdditionalContextBytes": 7327,
+ "claudeSystemAppendBytes": 5745,
+ "codexAdditionalContextBytes": 7541,
"mcpToolListJsonBytes": 48217
}
diff --git a/evals/mcp-tool-use/cases/visual-preview.ndjson b/evals/mcp-tool-use/cases/visual-preview.ndjson
index edd66f26966e9..42a66188cb364 100644
--- a/evals/mcp-tool-use/cases/visual-preview.ndjson
+++ b/evals/mcp-tool-use/cases/visual-preview.ndjson
@@ -34,3 +34,4 @@
{"id": "browser-status-reuses-existing-tab", "area": "visual-preview", "kind": "control", "privacy": "commit-safe", "pattern": "usable Browser tab needs no new tab", "experience": "An already paired, usable tab should be reused during verification.", "source": {"report": "Maintainer browser verification failure, 2026-11-03"}, "setup": {"role": "top", "context": "The UI fix is complete. The test server is healthy at http://localhost:5173 and its browser profile is already paired. The current tab can be automated.", "mocks": {"preview_status": {"available": true, "visible": true, "tabId": "tab_1", "url": "http://localhost:5173/", "tabs": [{"tabId": "tab_1", "url": "http://localhost:5173/", "owner": "agent", "ownedByCaller": true}], "control": {"owner": "agent", "ownedByCaller": true, "generation": 0}}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5173/"}, "preview_snapshot": {"available": true, "tabId": "tab_1", "url": "http://localhost:5173/", "screenshotPath": "/tmp/after.png"}}, "priorTurns": [{"role": "user", "text": "Use the Browser panel to verify the UI fix and capture an after screenshot."}, {"role": "assistant", "toolCalls": [{"tool": "Bash", "input": {"command": "cat .agents/skills/test-t3-app/SKILL.md"}, "result": "", "resultFile": ".agents/skills/test-t3-app/SKILL.md"}]}, {"role": "assistant", "toolCalls": [{"tool": "preview_status", "input": {}, "result": {"available": true, "visible": true, "tabId": "tab_1", "url": "http://localhost:5173/", "tabs": [{"tabId": "tab_1", "url": "http://localhost:5173/", "owner": "agent", "ownedByCaller": true}], "control": {"owner": "agent", "ownedByCaller": true, "generation": 0}}}]}], "harness": "claude"}, "prompt": "", "expect": {"calls": [{"tool": "preview_snapshot"}], "callsMustNot": [{"tool": "preview_open", "args": {"reuseExistingTab": false}}]}, "maxSteps": 10, "notes": "Checks reuse of an existing usable tab. Screenshot saving is outside this control. Prior tool-result replay is validated on the Claude harness; Codex output wrapping needs separate validation."}
{"id": "browser-resume-after-unavailable-status", "area": "visual-preview", "kind": "bad", "privacy": "commit-safe", "pattern": "closed Browser panel treated as unavailable automation", "experience": "The agent read test-t3-app, saw available false with no tab, and asked the user to open the panel instead of calling preview_open. Opening a tab succeeded on the next turn.", "source": {"report": "Maintainer browser verification failure, 2026-11-03; fresh skill read followed by unavailable status. Opus replay reproduces the stop on the old skill."}, "setup": {"role": "top", "context": "Repository instructions: Before web or desktop UI verification, read .agents/skills/test-t3-app/SKILL.md and follow it. If a skill causes you to stop or leave requested work unfinished, link the SKILL.md, quote the relevant instruction, and explain how it applies.", "mocks": {"preview_status": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}, "preview_open": {"available": true, "visible": false, "tabId": "tab_1", "url": "http://localhost:5173/"}, "preview_snapshot": {"available": true, "tabId": "tab_1", "url": "http://localhost:5173/", "screenshotPath": "/tmp/after.png"}}, "world": {"repo": "acme/app", "branch": "fix/permissions-popup", "status": "", "files": {"apps/web/src/components/ComposerContextPermissions.tsx": "export function ComposerContextPermissions() { return ; }\n"}, "description": "The requested fix is already committed as a1b2c3d on fix/permissions-popup, ahead of main. It changes ComposerContextPermissions.tsx to open the permissions popup above its trigger. Dependencies are installed. No development server is running and no .t3 directory exists.", "commits": [{"sha": "a1b2c3d", "subject": "fix(web): open composer permissions above its button"}]}, "priorTurns": [{"role": "user", "text": "When I click Full access in the composer, open its permissions menu directly above that button."}, {"role": "assistant", "toolCalls": [{"tool": "Bash", "input": {"command": "git commit -am \"fix(web): open composer permissions above its button\""}, "result": "[fix/permissions-popup a1b2c3d] fix(web): open composer permissions above its button\n 1 file changed, 3 insertions(+), 1 deletion(-)"}]}, {"role": "assistant", "text": "Implemented. Clicking Full access opens the permissions menu above the button. Focused checks pass. Full-client visual verification remains undone."}, {"role": "user", "text": "before/after screenshot"}, {"role": "assistant", "toolCalls": [{"tool": "Bash", "input": {"command": "cat .agents/skills/test-t3-app/SKILL.md"}, "result": "", "resultFile": ".agents/skills/test-t3-app/SKILL.md"}]}, {"role": "assistant", "text": "I'll use the test-t3-app skill to capture the before and after states in the app."}, {"role": "assistant", "toolCalls": [{"tool": "preview_status", "input": {}, "result": {"available": false, "visible": false, "tabId": null, "url": null, "title": null, "loading": false, "profiles": [{"id": "default", "name": "Default"}, {"id": "incognito", "name": "Incognito", "incognito": true}], "defaultProfileId": "default"}}]}], "shellMocks": [{"match": "git status|ls .*\\.t3", "output": "ls: .t3: No such file or directory"}], "harness": "claude"}, "prompt": "", "expect": {"calls": [{"tool": "preview_open"}]}, "maxSteps": 8, "notes": "Resume at the failed decision after reading the skill and receiving available=false. Opus reproduces the premature stop; Sonnet did not reliably reproduce it. This checks recovery through preview_open, not completion of screenshots. Prior tool-result replay is validated on the Claude harness; Codex output wrapping needs separate validation."}
{"id": "revise-page-resends-html", "area": "visual-preview", "kind": "bad", "pattern": "revising a page on disk resends its full HTML to html_preview and html_render", "experience": "The pages already existed as generated files on disk, but the agent retyped each one into html_preview and again into html_render, about four minutes per call. To save time it published fixes without previewing them again.", "source": {"discussion": "https://github.com/pingdotgg/t3code/discussions/23303"}, "rule": "edit the .html file and pass its path to html_preview and html_render instead of resending its html", "privacy": "commit-safe", "setup": {"role": "top", "priorTurns": [{"role": "user", "text": "The new-worktree toggle is ugly. Come up with four better options and mock them."}, {"role": "assistant", "text": "Here are four options.", "toolCalls": [{"tool": "Write", "input": {"file_path": "/tmp/design/index.html", "content": "
\n\n", "title": "New worktree: four options", "height": 1840}, "result": "{\"htmlRender\":{\"attachmentId\":\"att-1\",\"title\":\"New worktree: four options\",\"height\":1840},\"message\":\"Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say.\"}"}]}], "mocks": {"html_preview": {"width": 728, "contentHeight": 1840, "capturedHeight": 1840, "consoleMessages": []}, "html_render": {"htmlRender": {"attachmentId": "att-1", "title": "New worktree: four options", "height": 1840}, "message": "Shown to the reader above your reply. Don't mention or describe the page; reply with only what it doesn't already say."}}, "world": {"files": {"/tmp/design/index.html": "\n
A. Checkbox under the composer
Ask anything
\n
B. Toggle in the toolbar
Ask anything
\n
C. Split send button
Ask anything
Send in new worktree
\n
D. Two visible choices
Ask anything
\n\n"}}}, "prompt": "Make the Send in new worktree button in option C blue, and show me the page again.", "expect": {"calls": [{"tool": "html_preview", "args": {"path": "/tmp/design/index.html", "html": null}}, {"tool": "html_render", "args": {"path": "/tmp/design/index.html", "html": null}}], "callsMustNot": [{"tool": "html_preview", "args": {"html": "~Ask anything"}}, {"tool": "html_render", "args": {"html": "~Ask anything"}}], "order": ["html_preview", "html_render"]}, "notes": "On main neither tool has a path parameter."}
+{"id": "invoked-skill-uses-separate-browser", "area": "visual-preview", "kind": "bad", "pattern": "an invoked skill names a separate signed-in browser but the agent insists on preview tools", "experience": "The user invoked a skill whose instructions named another browser. The agent refused to follow it because T3's orchestration text said preview tools only.", "rule": "Another browser named by an invoked skill: use it, not preview, and report any prerequisite it is missing.", "privacy": "commit-safe", "setup": {"role": "top", "context": "The user has invoked member-portal-browser. Its loaded SKILL.md instructions are: For the signed-in Acme member portal, use `portal-browser --session member read `. This helper controls a separate browser with the user's existing sign-in. It opens the URL and returns the visible page text. The helper is installed, the member session is signed in, and no prerequisite is missing.", "shellMocks": [{"match": "portal-browser\\s+--session\\s+member\\s+read\\s+https://members\\.acme\\.example/announcements/latest", "output": "Latest announcement: Scheduled maintenance\nThe member portal will be unavailable on Friday from 02:00 to 03:00 UTC."}], "world": {"repo": "acme/app", "branch": "main", "status": "", "description": "portal-browser is installed and its member session is signed into https://members.acme.example. The read command uses that separate browser to return visible page text."}}, "prompt": "/member-portal-browser Read the latest announcement at https://members.acme.example/announcements/latest and tell me when the portal will be unavailable.", "expect": {"notCalls": ["preview_status", "preview_open", "preview_dialog", "preview_navigate", "preview_resize", "preview_set_appearance", "preview_snapshot", "preview_act", "preview_evaluate", "preview_wait_for", "preview_recording_start", "preview_recording_stop"], "shellMust": ["portal-browser\\s+--session\\s+member\\s+read\\s+https://members\\.acme\\.example/announcements/latest"], "finalTextMust": ["[Ff]riday", "02:00", "03:00"]}, "source": {"restoration": "https://github.com/pingdotgg/t3code/pull/16956", "regression": "https://github.com/pingdotgg/t3code/pull/24850"}, "notes": "Authored generic skill, site and helper. Uses the default browser-access state: Codex input hard-codes browser:true, so this case does not model browser access off. The required helper command and final announcement details prevent a refusal from passing.", "maxSteps": 8}
diff --git a/packages/provider-core/src/server/orchestrationInstructions.ts b/packages/provider-core/src/server/orchestrationInstructions.ts
index 9c9720edf18b7..e321ba95f15cf 100644
--- a/packages/provider-core/src/server/orchestrationInstructions.ts
+++ b/packages/provider-core/src/server/orchestrationInstructions.ts
@@ -21,7 +21,7 @@ Ordinary coding tasks need no T3 tools.
`### Visuals and preview
- Answer data with a trend or comparison with a chart, and show any visual the user asks for here: pass the path to an HTML file, or inline HTML to \`html_preview\`, then \`html_render\`. A screenshot on disk or a text description does not show it. Never start a server for it. Page tools write only on user request. Never use them to show results. Asked for an image to post, send, or use elsewhere: a \`.png\` file and its path, never \`html_render\`.
-- Never use \`preview_*\` unasked to verify or show your own edit. Use them when the current request asks for browser work, including recording edits, or names a page or tab. Do not repeat preview calls from an earlier turn. Public pages: web fetch or search unless browser work was asked; then preview tools only, no web fetch. Close a tab you opened before you reply, unless the user asked for it or uses it.`,
+- Never use \`preview_*\` unasked to verify or show your own edit. Use them when the current request asks for browser work, including recording edits, or names a page or tab. Do not repeat preview calls from an earlier turn. Public pages: web fetch or search unless browser work was asked; then preview tools only, no web fetch. Another browser named by the user, an invoked skill, or a documented repo workflow: use it, not preview, and report any prerequisite it is missing. Close a tab you opened before you reply, unless the user asked for it or uses it.`,
];
export const T3_CODE_ORCHESTRATION_INSTRUCTIONS = `
@@ -33,7 +33,7 @@ export const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = `
## T3 Code browser
-When browser work was asked, \`preview_*\` is the browser you share with the user: \`preview_status\` first, then \`preview_open\` if no tab can be automated. Prefer snapshot locators over coordinates. Switch browsers only if the tools are absent or \`preview_open\` says unsupported, the user asks, or two preview calls fail on one step. Then quote the error and switch.
+When browser work was asked, \`preview_*\` is the browser you share with the user: \`preview_status\` first, then \`preview_open\` if no tab can be automated. Prefer snapshot locators over coordinates. Switch browsers only if the tools are absent or \`preview_open\` says unsupported, the user, an invoked skill, or a documented repo workflow names another browser, or two preview calls fail on one step. Then quote the error and switch.
`;
const T3_CODE_DEVICE_TOOLS = ["device_list", "device_open", "device_screenshot", "device_close"];
From c5f64391723d71a720a380d904970b2cb6452e8d Mon Sep 17 00:00:00 2001
From: mrroll <58090032+mrroll@users.noreply.github.com>
Date: Wed, 11 Nov 2026 17:08:23 +0800
Subject: [PATCH 2/2] fix(server): only the user or a skill they invoked names
another browser
A review of the pull request pointed out that "a documented repo workflow" let the docs of any cloned repository send an agent to another browser, possibly the user's signed-in one, without the user asking. The exception now names two sources: the user, or a skill they invoked. That covers the reported case and keeps the choice with the user.
The list of conditions for switching browsers uses semicolons so that a model can parse it, and it says "Quote any error, then switch", because the named-browser case has no error to quote.
The test asserts the new phrase, and the context budget is set to the measured sizes: 5720 and 7485 bytes.
Claude Fable 5.2 with Claude Opus 5.5 subagents, in T3 Code.
---
apps/server/src/provider/CodexDeveloperInstructions.test.ts | 2 +-
apps/server/src/provider/agentContextBudget.json | 4 ++--
.../provider-core/src/server/orchestrationInstructions.ts | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/apps/server/src/provider/CodexDeveloperInstructions.test.ts b/apps/server/src/provider/CodexDeveloperInstructions.test.ts
index 21860598a8190..5fc45a2a2d964 100644
--- a/apps/server/src/provider/CodexDeveloperInstructions.test.ts
+++ b/apps/server/src/provider/CodexDeveloperInstructions.test.ts
@@ -75,7 +75,7 @@ describe("Codex orchestration context", () => {
orchestration.includes("Every new delegate review or re-check is a new `delegate_task`"),
);
NodeAssert.ok(orchestration.includes("never a `t3_thread_send`"));
- NodeAssert.ok(orchestration.includes("invoked skill"));
+ NodeAssert.ok(orchestration.includes("a skill they invoked"));
NodeAssert.ok(!orchestration.includes("inheritedOptions"));
NodeAssert.ok(!orchestration.includes("Prefer native"));
// The ACP terminal fallback is ACP-only; Codex agents never need it.
diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json
index bd87b82d92d86..1d58e9a9836d7 100644
--- a/apps/server/src/provider/agentContextBudget.json
+++ b/apps/server/src/provider/agentContextBudget.json
@@ -1,5 +1,5 @@
{
- "claudeSystemAppendBytes": 5745,
- "codexAdditionalContextBytes": 7541,
+ "claudeSystemAppendBytes": 5720,
+ "codexAdditionalContextBytes": 7485,
"mcpToolListJsonBytes": 48217
}
diff --git a/packages/provider-core/src/server/orchestrationInstructions.ts b/packages/provider-core/src/server/orchestrationInstructions.ts
index e321ba95f15cf..ff1499c17c68b 100644
--- a/packages/provider-core/src/server/orchestrationInstructions.ts
+++ b/packages/provider-core/src/server/orchestrationInstructions.ts
@@ -21,7 +21,7 @@ Ordinary coding tasks need no T3 tools.
`### Visuals and preview
- Answer data with a trend or comparison with a chart, and show any visual the user asks for here: pass the path to an HTML file, or inline HTML to \`html_preview\`, then \`html_render\`. A screenshot on disk or a text description does not show it. Never start a server for it. Page tools write only on user request. Never use them to show results. Asked for an image to post, send, or use elsewhere: a \`.png\` file and its path, never \`html_render\`.
-- Never use \`preview_*\` unasked to verify or show your own edit. Use them when the current request asks for browser work, including recording edits, or names a page or tab. Do not repeat preview calls from an earlier turn. Public pages: web fetch or search unless browser work was asked; then preview tools only, no web fetch. Another browser named by the user, an invoked skill, or a documented repo workflow: use it, not preview, and report any prerequisite it is missing. Close a tab you opened before you reply, unless the user asked for it or uses it.`,
+- Never use \`preview_*\` unasked to verify or show your own edit. Use them when the current request asks for browser work, including recording edits, or names a page or tab. Do not repeat preview calls from an earlier turn. Public pages: web fetch or search unless browser work was asked; then preview tools only, no web fetch. Another browser named by the user or a skill they invoked: use it, not preview, and report any prerequisite it is missing. Close a tab you opened before you reply, unless the user asked for it or uses it.`,
];
export const T3_CODE_ORCHESTRATION_INSTRUCTIONS = `
@@ -33,7 +33,7 @@ export const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = `
## T3 Code browser
-When browser work was asked, \`preview_*\` is the browser you share with the user: \`preview_status\` first, then \`preview_open\` if no tab can be automated. Prefer snapshot locators over coordinates. Switch browsers only if the tools are absent or \`preview_open\` says unsupported, the user, an invoked skill, or a documented repo workflow names another browser, or two preview calls fail on one step. Then quote the error and switch.
+When browser work was asked, \`preview_*\` is the browser you share with the user: \`preview_status\` first, then \`preview_open\` if no tab can be automated. Prefer snapshot locators over coordinates. Switch browsers only if: the tools are absent or \`preview_open\` says unsupported; the user or an invoked skill names another browser; or two preview calls fail on one step. Quote any error, then switch.
`;
const T3_CODE_DEVICE_TOOLS = ["device_list", "device_open", "device_screenshot", "device_close"];