Skip to content

docs(agent-tool): non-tool-calling models fail every send; no safe client-side fix - #2787

Closed
IngTian wants to merge 7 commits into
openchamber:mainfrom
IngTian:fix/agent-tool-model-toolcall-gate
Closed

IngTian wants to merge 7 commits into
openchamber:mainfrom
IngTian:fix/agent-tool-model-toolcall-gate

Conversation

@IngTian

@IngTian IngTian commented Aug 10, 2026 •

Copy link
Copy Markdown

Draft. The client-side fix this PR originally proposed does not work, and no safe substitute exists — the real fix is upstream, now submitted as anomalyco/opencode#41463 (issue #41464). What remains here is documentation of the constraint. Not proposing a behavior change.

Intent

Report a real user-facing bug and document why it cannot be fixed from OpenChamber.

OpenChamber injects its openchamber control tool into every managed OpenCode process, at process start, before any model is known. When the user selects a model that cannot call tools, Vertex Gemini image models ("Nano Banana", gemini-2.5-flash-image) reject every send:

Unable to submit request because the model does not support function calling.

The injected tool makes this unavoidable through configuration. Stock agents begin with "*": "allow", and a user who denies every tool by name still cannot deny openchamber, because nothing in the UI reveals it exists. The only working workaround is a hand-written agent with permission: {"*": deny} — not discoverable.

What changed

Documentation only, plus an exported constant so the plugin generator stops using a bare literal for its own tool ID.

Net diff versus the base: +40 / -2 across 3 files, all in packages/web/server/lib/agent-tool/.

Non-goals

  • Fixing the bug. Three commits attempted it; all are reverted in 6b8f8e69.
  • Any behavior change. No runtime code path is altered.

Why there is no safe client-side fix

The only lever OpenCode exposes is the per-send tools map. It converts that map into the session permission ruleset and persists it, replacing prior contents. Every variant fails — each verified against opencode 1.18.15 by capturing the outgoing provider request:

Attempt Result
{"openchamber": false} Removes one declaration; ~11 stock built-ins still reach the provider. Send still fails.
{"*": false} Suppresses the whole set (0 declarations, confirmed). But the deny persists — a later send on a tool-calling model in the same session also gets no tools.
{"*": true} to recover Privilege escalation. Permission rules are evaluated for approval as well as tool filtering, so a wildcard allow turns an agent's edit: deny into allow and bash: ask into allow, suppressing the approval prompt.
session.update({permission}) Accepts the field but merges rather than replaces, so it cannot clear a deny.

Suppressing tools must never widen permissions, which rules out the only recovery path. session.command and session.shell could not have been covered regardless — the OpenCode API has no tools field on either route.

The fix, upstream

Submitted as anomalyco/opencode#41463 against dev (issue #41464). One guard in resolveTools:

if (!input.model.capabilities.toolcall) return {}

capabilities.toolcall is populated from provider config and never read in OpenCode's request path. This covers every send path for every OpenCode client, with no permission side effects. Verified there by request capture: a Vertex image model sends no tools field (was 12 declarations), a tool-calling model still sends all 12.

Once a release carrying it ships, image models work in OpenChamber with any agent and no configuration, and this PR's documentation becomes history rather than an active constraint.

Follow-up once the upstream fixes land

Two separate OpenCode bugs sit between a user and a working image generation. Both are upstream; neither needs OpenChamber code.

# Bug Upstream status
1 Tool definitions sent to models that can't call tools → Vertex rejects every request #41463 open against dev (issue #41464)
2 Generated images discarded — SessionProcessor has no case "file", so Gemini inlineData never becomes a message part #41468 open against dev (issue #41467). #40126 covers v2, which does not reach dev releases

They are sequential, not alternatives: #1 lets the request through, #2 makes the result visible. Fixing only #1 yields a successful call whose image is silently dropped — the user is billed for output they never see. Fixing only #2 changes nothing, because the request still 400s.

What OpenChamber has to do

Nothing, as far as I can determine. I traced the receive path in this worktree:

  • event-reducer.ts:19 — SKIP_PARTS is patch, step-start, step-finish. file is not skipped.
  • event-reducer.ts:391 — the message.part.updated reducer is type-agnostic; it upserts any part by ID.
  • partUtils.ts:36 — filterVisibleParts filters synthetic text and reasoning, never file.
  • MessageBody.tsx:2230 — AssistantMessageBody already renders <MessageFilesDisplay files={parts} />, and FileAttachment.tsx:580 filters only on type === 'file' && (mime || url) with no role check.

So an assistant-side image part should render through the existing attachment display, the same way a user-attached image does today.

Unverified: this repo has no component-render harness for MessageBody, so the render leg is a code read rather than an executed test. The first real check is a live send once a release carries #41463 — if the image arrives as a part but does not display, the gap is in the render path above and that is the OpenChamber fix to write.

Order of work

  1. #41463 merges and ships. Then image models work with the stock build agent and no configuration.
  2. Delete the "Models that cannot call tools" section added by this PR, and close this PR. It documents a constraint that no longer exists.
  3. Bug 2 is submitted as #41468 — SessionProcessor gains a case "file" that persists the media as a file part, plus assistant-media replay so follow-up edits ("make it brighter") work.
  4. Verify a real generation end to end in OpenChamber and fix the render path only if step 3 shows the part arriving without displaying.

Until step 1 ships, the only user-side workaround remains an agent with permission: {"*": deny} — see the doc section this PR adds.

Affected surfaces

Surface Effect
packages/web/server/lib/agent-tool/ Doc section + exported OPENCHAMBER_AGENT_TOOL_NAME, interpolated into the generated plugin instead of a bare literal
Runtime behavior, all runtimes None. No send path, request body, or persisted contract changes

Repository guidance

Guidance Why it applies How the change complies
AGENTS.md → "Enforce security and correctness in core/runtime logic" The rejected fix would have written permission rules Rejected precisely because it widened permissions; nothing ships
AGENTS.md → "Update owning documentation when contracts or invariants change" The constraint is a real invariant future editors will hit Recorded in the owning module's DOCUMENTATION.md
openchamber-change-discipline → smallest complete change The first attempt grew across two packages Reverted to docs plus one constant
openchamber-change-discipline → "Do not hide a required architectural migration behind a local heuristic" This is exactly that: an upstream gap Says so explicitly rather than shipping a partial client workaround

Validation

Check Result
bun run type-check (all 5 workspaces, incl. both VS Code projects) Clean
bun run lint (all 5 workspaces) Clean
bunx vitest run server/lib/agent-tool/runtime.test.js 22 pass
bun run docs:validate Passed — 450 pages, 45 sidebar links
Request capture vs. the real generated plugin Documented each attempt's outcome in the table above

Not verified: live Vertex traffic. The provider endpoint was a local recording proxy, so the 400 is reproduced by request shape rather than by Google's response. The permission-escalation finding was verified by simulating OpenCode's own evaluator against a restrictive agent, not by observing a suppressed prompt in the UI.

Visual evidence

None applicable — no rendered output, copy, or interaction changes.

Risks and failure behavior

The documented bug is unfixed and still reachable: any send to a non-tool-calling model fails until the upstream change lands, and the only user workaround remains a hand-written wildcard-deny agent.

The doc change carries no runtime risk. The exported constant is consumed only inside its own module and its test.

Corrections to earlier revisions of this PR

Stated as fact earlier and wrong:

  1. "tools does not filter one request." It does both — it is stored on the user message and filters that request, and replaces the persisted ruleset.
  2. "No API exists to set session permissions." session.update accepts permission; I read the wrong SDK path.
  3. "VS Code: unaffected." The VS Code webview is built from packages/ui/src and uses the same send path, so the gate would have run there against an OpenCode that never had the tool injected.
  4. "The name cannot drift." Nothing bound the UI copy to the server constant — and a later commit did drift them, silently.
  5. The end-to-end evidence was reported as showing the fix worked. It showed only that one declaration was absent; the same capture still carried 11 others.

… support

OpenChamber injects its `openchamber` control tool into every managed OpenCode
process. Injection happens at process start, before any model is known, so the
tool is declared even when the selected model cannot call tools. Providers that
reject function calling outright then fail every send — Vertex Gemini image
models ("Nano Banana") return:

  Unable to submit request because the model does not support function calling

The model's own `tool_call: false` does not help: OpenCode assigns
`capabilities.toolcall` but never reads it to gate a request. Tools are filtered
only by permission name, and no built-in agent denies `openchamber`, so the
injected tool survives every stock agent. Today the only workaround is a
hand-written agent with a wildcard `permission: {"*": deny}`, which no ordinary
user discovers.

Shared UI now sends OpenCode's per-send `tools` map, derived from the selected
model's `capabilities.toolcall`.

Two constraints shape the contract, both verified against opencode 1.18.15:

- `tools` does not filter one request. OpenCode converts it into the session's
  permission ruleset and persists it, replacing whatever was there. A one-shot
  deny would strand the tool off for the rest of the session, so the complete
  desired map is sent on every send and model switches self-correct.
- When `agentControlToolEnabled` is false the tool was never injected, so no
  `tools` field is sent and session permissions are left untouched.

Unknown capability is treated as tool-capable: model metadata may not have
loaded yet, and defaulting to disabled would silently remove the tool from
models that support it.

The tool ID is now exported as `OPENCHAMBER_AGENT_TOOL_NAME` and interpolated
into the generated plugin, so the name the client gates and the name the plugin
registers cannot drift.

Verified end to end against a recording proxy with the real generated plugin
loaded: with the gate, `openchamber` is absent from the Vertex request's
`functionDeclarations`; sending the map with the tool enabled restores it in the
same session.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@IngTian
IngTian requested a review from a team August 10, 2026 00:38
@IngTian
IngTian requested a review from btriapitsyn as a code owner August 10, 2026 00:38
@openchamber-bot openchamber-bot Bot added the review:pending Automated contribution review is in progress. label Aug 10, 2026
@openchamber-bot

Copy link
Copy Markdown
Contributor

Code Review Summary

This PR fixes a real bug: OpenChamber injects its openchamber control tool into every managed OpenCode process before any model is known, so providers that reject function calling (Vertex Gemini image models) fail every send. The fix adds a per-send tools map — derived from the selected model's capabilities.toolcall via getModelMetadata(...).tool_call — on the normal prompt path (routeMessage → opencodeClient.sendMessage → SDK promptAsync), and exports the tool name from the server so the plugin registration and the client gate share one constant.

I verified the core contract against the actual pinned stack (@opencode-ai/[email protected] types and the OpenCode 1.18.15 server source, not just the PR's claims): SessionPromptData.body.tools is an official SDK field; prompt_async routes into the same prompt() which converts each tools entry into a session permission rule ({permission, action: allow|deny, pattern: "*"}); a deny rule makes registry.tools drop the tool, so it never reaches the provider's functionDeclarations. The implementation is correct and proportionate for the pinned version, the tests are focused and meaningful, and the PR handoff is thorough. No blockers found.

Verdict: PASS

Reviewed HEAD: 64de5b932a5a35e50e715d162d5bc71fa485238a
Previous reviewed HEAD: none

Applied Repository Guidance

Source Why applicable Rules/invariants evaluated
AGENTS.md Any OpenChamber source change; the bug is a runtime-contract failure, not a display issue "Prefer authoritative state over heuristics" (gate uses capabilities.toolcall, not model-name matching); "Enforce security and correctness in core/runtime logic" (gate lives in the shared send path); "Runtime-specific differences must be intentional and visible" (VS Code/external OpenCode not injected); "Never let fetch failure masquerade as empty success" (not implicated)
CONTRIBUTING.md + .github/PULL_REQUEST_TEMPLATE.md Contribution-contract and evidence requirements All template sections completed with concrete content; validation states exact commands, results, and what was not verified; visual-evidence section gives a concrete reason why no screenshot is feasible (request-level change, needs live Vertex credentials); risks/rollback section covers the persisted-ruleset contract
.agents/skills/openchamber-change-discipline/SKILL.md Source, export, persisted-contract, and documentation change Smallest-complete-change; persisted/external-contract analysis (session ruleset is OpenCode-owned persisted data); new files/exports require bun run dead-code (claimed run, no findings); owning DOCUMENTATION.md updated
.agents/skills/ui-api-decoupling/SKILL.md (+ references/runtime-parity.md, references/implementation-map.md) Shared-UI SDK call change (tools on promptAsync) Official OpenCode API goes through opencodeClient/SDK verbatim (no hand-built request); runtime state resolved at call time inside send; parity for web/desktop/VS Code/mobile considered and documented; VS Code route decisions consulted
packages/web/server/lib/agent-tool/DOCUMENTATION.md Owning module doc for the injected tool Updated with "Per-send model gating" section; runtime-parity section (VS Code/external not injected) read for the affected-surfaces claims
packages/ui/src/sync/DOCUMENTATION.md Send-path ownership (session-ui-store.ts is the SDK-facing send entrypoint) No sync/reducer state added by the gate; send path ownership respected
External verification: @opencode-ai/[email protected] type definitions and OpenCode v1.18.15 server source (session/prompt.ts, session/tools.ts, permission/index.ts, session/server handlers, SDK types.gen.ts) The PR's central claim — that per-send tools maps to a persisted, replacing session permission ruleset — is a claim about OpenCode behavior Confirmed: tools → ruleset replacement + setPermission persistence; prompt_async honors tools; deny rules filter tools from registry.tools. Also surfaced two facts the PR does not mention: tools is marked @deprecated in 1.18.15, and the supported replacement (session.update with permission) merges instead of replacing

Findings

  1. non-blocker: the fix is built on an OpenCode field that 1.18.15 already marks deprecated
    File: packages/ui/src/sync/agent-tool-gate.ts (and module doc packages/web/server/lib/agent-tool/DOCUMENTATION.md)
    Problem: OpenCode 1.18.15 annotates PromptInput.tools with @deprecated tools and permissions have been merged, you can set permissions on the session itself now, and the supported path (session.update with a permission payload) merges rather than replaces. The PR's compatibility section only addresses older OpenCode versions, not the deprecation trajectory. If a future OpenCode/SDK bump removes or alters the field, the gate silently stops working and image-model sends regress with no error signal. Works today against the pinned 1.18.15, so this does not block, but the module doc should state the deprecation and the migration path.
    Suggested fix: document the deprecation and the session.update permission alternative in the "Per-send model gating" doc section; track switching to session.update when the field is removed.

  2. non-blocker: every gated send replaces the session permission ruleset; the "Unaffected" claims for external OpenCode and VS Code are overbroad at the request level
    File: packages/ui/src/sync/session-ui-store.ts:220 (gate wiring)
    Problem: verified in session/prompt.ts that a send carrying tools sets session.permission = permissions (replace + persist), whereas session.update permission merges with the existing ruleset. The PR's mitigation ("complete desired map is sent every time") is only complete for the openchamber slot — any other rule in the session ruleset is erased on the next normal send. Today the managed-case ruleset is otherwise empty (OpenChamber never writes it), so there is no user-visible regression; but (a) any other session-permission writer (session.update PATCH, session.create with permission) is silently clobbered, and (b) both external OpenCode (OPENCODE_HOST) and VS Code sends flow through the same shared store path (verified: packages/vscode/webview/main.tsx → useSessionUIStore.sendMessage → routeMessage), so their requests do rewrite the ruleset to [{openchamber, allow|deny, "*"}] on every send — the rule is inert there (tool not injected), but "Unaffected" in the affected-surfaces table is inaccurate at the request level. Not a security issue: a wiped deny downgrades to ask, which re-prompts rather than silently granting.
    Suggested fix: correct the affected-surfaces/risk wording to state that external OpenCode and VS Code sends still carry the map and rewrite the (usually empty) ruleset, and note the replace-vs-merge difference versus session.update.

  3. non-blocker: reviewFlow and MultiRunFusion sends bypass the gate
    File: packages/ui/src/lib/reviewFlow.ts:390, packages/ui/src/components/multirun/MultiRunFusionDialog.tsx:169
    Problem: both call opencodeClient.sendMessage directly and never resolve resolveAgentToolGate, so a send targeting a non-tool-calling model from auto-review or multirun fusion still declares the tool and hits the same provider rejection the PR fixes. The PR explicitly documents the slash-command and shell exclusions but not these two normal-prompt paths. Scope decision, not a defect, but it should be stated (or these paths gated, since they are normal prompts).
    Suggested fix: either route these through the gate (they have providerID/modelID at hand) or add them to the documented non-goals.

  4. non-blocker: no test covers the store-level glue
    File: packages/ui/src/sync/session-ui-store.ts (gate wiring); existing session-ui-store.test.js "routeMessage skill invocation" block already captures sendMessage params
    Problem: the pure-function tests (agent-tool-gate.test.ts) and the client.test.ts forwarding tests are good, but nothing asserts that routeMessage passes the resolved tools map (from getModelMetadata(...).tool_call and agentControlToolEnabled) into sendMessage. A regression in the metadata read, the setting read, or the parameter wiring would pass CI.
    Suggested fix: one store-level test in session-ui-store.test.js asserting tools is { openchamber: true|false } / absent for the three gate states.

Evidence and Residual Risk

  • Review evidence: the diff's tests are relevant and sufficient for the changed contract — 5 pure-function gate tests, 2 sendMessage forwarding tests (verbatim tools pass-through; field omitted when no gate), and 1 runtime test binding the exported name to the generated plugin's tool key. The PR's stated validation (focused unit runs, type-check/lint, dead-code, end-to-end via a recording proxy showing openchamber absent from functionDeclarations and restored in-session) is proportionate and current for this HEAD. No CI status was used in forming this verdict. I additionally verified the mechanism against the pinned OpenCode 1.18.15 SDK types and server source.
  • Security/supply-chain: no new dependencies, credentials, tokens, logs, or network surfaces. The gate can only deny OpenChamber's own tool relative to today's behavior; unknown-rule downgrade is deny→ask, never a silent grant. No supply-chain exposure.
  • Residual risk: I could not execute the PR branch or run its checks (read-only review). Live Vertex traffic was not exercised (the PR discloses this); the wipe-out finding (2) is real in the code but has no practical impact today because the managed-case session ruleset is otherwise empty — monitor any future session-permission writer, and re-check the tools field on the next OpenCode/SDK bump (finding 1).

@github-actions github-actions Bot added review:ready Ready for maintainer review. and removed review:pending Automated contribution review is in progress. labels Aug 10, 2026
Review follow-ups to the per-send tool gate.

Behavior fix — the gate no longer consults `agentControlToolEnabled`. That
setting only takes effect on the next managed OpenCode restart, so between
toggling it off and restarting, the tool is still injected while the gate went
silent — reintroducing the provider rejection on exactly the configuration the
user believes is disabled, with no restart badge to hint at it. Naming a tool
that was never injected is inert (OpenCode filters the tools that exist and
matches rule names as literal wildcards), so sending the map unconditionally is
safe and keeps the gate honest when intent and process disagree. This also
resolves the contradiction with the module's documented runtime parity, where
VS Code and external OpenCode never receive the tool.

Documentation fix — the previous claim that `tools` "does not filter one
request" was wrong. It is stored on the user message and filters that request
(`session/llm/request.ts` `resolveTools` checks `user.tools?.[k] !== false`) and
it also replaces the persisted session ruleset. The operative guidance is
unchanged, but the load-bearing rationale was inaccurate.

Also documented, all verified against opencode 1.18.15:
- Ruleset replacement is wholesale, so OpenChamber assumes sole ownership of
  that slot; a rule set by another OpenCode client is discarded by the next send.
- Subagent sessions inherit `deny` rules from the parent, so a subagent spawned
  while an image model was selected starts without the control tool.
- Slash-command and shell routes accept no `tools` field at all, and a command's
  pinned model outranks the client's, so those paths stay ungated pending an
  upstream API change.
- Upstream marks `tools` `@deprecated`; the non-deprecated path merges instead of
  replacing and so could never flip a deny back to allow.

Tests — adds the missing call-site coverage. Deleting the `tools:` block from
`routeMessage` previously left every test passing; `agent-tool-gate-wiring.test.ts`
now fails without it, and covers the model-switch sequence that justifies sending
the complete map. Drops a tautological `Object.keys` assertion that could only
fail when a stronger assertion in the same file already had.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@openchamber-bot openchamber-bot Bot added review:pending Automated contribution review is in progress. and removed review:ready Ready for maintainer review. labels Aug 10, 2026
Adversarial review found the previous approach did not fix the reported bug.

Vertex rejects a request because it carries function declarations at all, not
because it carries `openchamber`. Denying one tool removed one declaration and
left the ~11 stock built-ins in place, so the send still failed. My own
end-to-end capture showed this and I misread it: I asserted `openchamber` was
absent from `functionDeclarations` and treated that as success, when the field
was still present with `bash, edit, glob, grep, question, read, skill, task,
todowrite, webfetch, write`. Agent `permission` config cannot close the gap
either — stock agents begin with `"*": "allow"`.

The gate now sends `{"*": false}` for models that cannot call tools, which drops
the `tools` field from the provider request entirely, and `{"*": true}`
otherwise. Verified by request capture against the real generated plugin: 0
declarations when denied, all 12 restored on the next send when allowed, in the
same session.

Because the gate now governs every tool rather than OpenChamber's own, it no
longer depends on whether this module injected anything, so it is correct in
runtimes where the control tool is absent. That also removes the cross-package
duplication of the tool name — `OPENCHAMBER_AGENT_TOOL_NAME` is now only used
inside the server module that owns the plugin.

Documented, all verified against opencode 1.18.15:
- This is a client-side mitigation for the paths OpenChamber controls, not a
  complete fix. Server-side dispatchers (scheduled tasks, goal continuations,
  obligatory context) post `prompt_async` directly and stay ungated, though they
  inherit whatever ruleset the last gated send persisted.
- The complete fix belongs upstream: OpenCode has `capabilities.toolcall` and
  never reads it in the request path.
- `session.update` does accept `permission` (contrary to my earlier claim), but
  it merges rather than replaces, so it could never flip a deny back to allow.
  The deprecated `tools` field is the only lever that self-corrects.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@openchamber-bot openchamber-bot Bot removed the review:pending Automated contribution review is in progress. label Aug 10, 2026
@IngTian
IngTian marked this pull request as draft August 10, 2026 01:39
@github-actions github-actions Bot added review:automation-failed Automated review failed to complete and must be retried. and removed review:automation-failed Automated review failed to complete and must be retried. labels Aug 10, 2026
…annot work

Review found no safe client-side fix exists, so the gate is removed and the
findings are documented instead. What remains is the module doc plus an exported
tool-name constant used by the plugin generator.

The lever OpenCode exposes is the per-send `tools` map, which it converts into
the session permission ruleset and persists, replacing prior contents. Every
variant fails, each verified against opencode 1.18.15 by request capture:

- `{"openchamber": false}` — the original approach — removes one declaration
  while ~11 stock built-ins still reach the provider. Vertex rejects on the
  presence of any function declaration, so the send still fails. My earlier
  end-to-end check asserted only that `openchamber` was absent and I read that
  as success; the captured request still carried `tools` with 11 entries.
- `{"*": false}` does suppress the whole set (0 declarations, confirmed), but the
  deny persists: a later send on a tool-calling model in the same session also
  gets no tools.
- `{"*": true}` to recover is a privilege escalation, not a no-op. Permission
  rules are evaluated for approval as well as for tool filtering
  (`permission/index.ts` returns early on `action === "allow"`), so a wildcard
  allow converts an agent's `edit: deny` into `allow` and `bash: ask` into
  `allow`, suppressing the approval prompt. Confirmed by simulating the real
  evaluator against a restrictive agent. Suppressing tools must never widen
  permissions.
- `session.update` does accept `permission` (contrary to my earlier claim) but
  merges rather than replaces, so it cannot clear a deny either.

`session.command` and `session.shell` could not have been covered regardless —
the OpenCode API has no `tools` field on either route.

The fix belongs upstream, where the capability is already known: return no tools
when `!model.capabilities.toolcall`. That covers every send path for every client
with no permission side effects.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@IngTian IngTian changed the title fix(agent-tool): gate the OpenChamber control tool by model tool-call support docs(agent-tool): non-tool-calling models fail every send; no safe client-side fix Aug 10, 2026
Points at anomalyco/opencode#41463 and issue #41464 so a future maintainer can
tell whether the constraint still applies, and notes that the section becomes
history once a release carrying the fix ships.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
IngTian and others added 2 commits August 9, 2026 22:17
Image generation needs two OpenCode fixes, not one: the toolcall gate (#41463)
lets the request through, and a `case "file"` in SessionProcessor makes the
result visible. Fixing only the first yields a billed request whose image is
silently dropped.

Also records that OpenChamber needs no change to display generated images — the
receive path is type-agnostic — while flagging that the render leg is a code read
rather than an executed test, since there is no component-render harness.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The image-drop bug now has a dev-targeted PR (anomalyco/opencode#41468, issue
#41467) rather than only the v2-branch one, so both halves of the upstream fix
are traceable from here.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@IngTian

IngTian commented Aug 10, 2026

Copy link
Copy Markdown
Author

Note for anyone reading the automated review above: it reviewed 64de5b93, which is 7 commits behind the current HEAD (c5b4bf30). The code it evaluated has since been reverted — this PR is now documentation only (3 files, all under packages/web/server/lib/agent-tool/).

Its findings were the reason for the revert, and independent review found two further problems it did not catch:

  • Gating only openchamber never fixed the bug. Vertex rejects on the presence of any functionDeclarations, and stock agents start from "*": "allow", so ~11 built-ins still reached the provider. My original end-to-end evidence asserted only that openchamber was absent and I misread that as success.
  • The obvious follow-up, {"*": true}, is a privilege escalation rather than a no-op: Permission.evaluate returns early on allow, so it converts an agent's edit: deny to allow and bash: ask to allow, suppressing the approval prompt.

Since suppressing tools must never widen permissions, and {"*": false} alone persists as a sticky deny, there is no safe client-side fix. The real fix is upstream and is now submitted: anomalyco/opencode#41463 (the rejection) and #41468 (the dropped image). This PR just records the constraint until a release carries them.

@openchamber-bot openchamber-bot Bot added merge-conflict:true Whether the PR has a merge conflict labels Aug 13, 2026
@IngTian IngTian closed this by deleting the head repository Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-conflict:true Whether the PR has a merge conflict

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant