You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
policy: deny shell * on custom agent breaks free tier with "can only be used from within OpenCode" #50627
Enabling permissions: [{action: shell, resource: "*", effect: deny}] on a custom primary agent makes every request fail on all free-tier models with OpenCode's free tier can only be used from within OpenCode, even though the request originates from inside OpenCode TUI.
Send any message to that agent on any Console free-tier model via TUI.
Observe Error: Error from provider (Console): OpenCode's free tier can only be used from within OpenCode.
Comment out the shell: "*" deny lines -> works again.
Expected Behavior
Shell deny should only block shell tool calls; the LLM request itself should still authenticate as in-OpenCode free-tier use.
Actual Behavior
Entire session fails with free-tier origin error on every message, before any tool use. Always reproducible; removal fixes. Affects all free models. Error is single line only.
Additional evidence — I independently hit this exact trigger and isolated the mechanism. This is the same root cause as #50806 / #49588 (compaction) / #49756, and it is not specific to the permission frontmatter or to shell-* denial specifically: the gate keys off the absence of the bash tool in the request body.
opencode run --agent t2 -m opencode/big-pickle "hi" → ❌ OpenCode's free tier can only be used from within OpenCode
Same agent with "tools": { "bash": false } removed → ✅ works
A different custom agent with a planner-style system prompt (full toolset) → ✅ works
Environment: opencode 1.18.21 and 1.18.32 (repro'd on both, so it is not a version-string gate), Windows, npm global install, provider=opencode (Console), model big-pickle, both TUI and headless opencode run.
Wire-level proof
I pointed the Console provider at a local mock endpoint and captured the exact requests for the working vs failing agent. The two requests are byte-identical in every header — authorization, user-agent: opencode/1.18.32 ai-sdk/provider-utils/4.0.40 runtime/bun/1.3.14, x-opencode-client, x-opencode-project, x-opencode-session, x-opencode-request all match. The only difference is the request body:
tools in request
result
working agent
35 tools, incl. bash
✅
failing agent
34 tools, no bash
❌ FreeTierError
Same binary, same account, same project, same model — a byte-level diff of the two HTTP requests reduces entirely to the tools array missing bash.
Real-world impact
Any agent that denies bash/shell is fully blocked on the free tier:
OhMyOpenAgent's "Prometheus - Plan Builder" agent (permission: { "bash": "deny", "interactive_bash": "deny" } for plan-mode-only) fails 100% of requests, while its main agent (bash present) works fine on the same provider/model/machine.
The free-tier origin check should not depend on the request's tools array (any tool-restricted agent, including legitimate read-only/plan agents, gets a false "outside OpenCode" verdict). A signed session/ticket or the existing x-opencode-session/x-opencode-request headers validated server-side would be a more robust signal. Happy to provide the full capture if useful.
Spawning it via the subagent tool fails immediately:
AI.Error.Authentication: Error from provider (Console): OpenCode's free tier can only be used from within OpenCode
at SessionStep.attempt (...) -> SessionRunner.runStep (...) -> SessionRunner.drain (...)
Same agent, same parent session, same model, same task — the only variable changed between pass and fail is the bash line.
Bisect
Four agents differing only in the bash line, all mode: subagent, spawned in alternating order from one parent session:
bash value
Result
key absent entirely
works
ask
works
deny
fails
Two things this adds on top of the V2 repro above:
Omitting the bash key entirely also works — so the full permission block minus bash is fine, and no other key in the block is implicated. Only the allow -> deny transition trips it.
bash: ask works. That suggests the gate may be reacting to tool removal rather than to shell permissions as such.
Two scope notes that may help triage
The failure is specific to the subagent tool path. The same agent selected and used as a primary agent (via the agent picker) works normally and has done for some time. Only the child-session path fails. That also explains why this can go unnoticed for a while — the interactive path is the one people actually use.
#51315 appears to have identified the actual mechanism: the gate runs server-side on Zen and rejects requests whose tools array is missing read or carries 0-3 tools, and disabling edit/write/apply_patch is harmless. If that's right, the deny is changing the tools array in some way that trips the same check, and the fix belongs in the gate rather than in permission normalization.
One suggestion on the error message itself: because it surfaces as AI.Error.Authentication and names billing, the natural first debugging step is to check the account and re-login, which is always a dead end here. An error that said "the configured permissions produced a request the free tier rejected" would save a lot of that.
Workaround
bash: ask instead of bash: deny. Confirmed working on 1.18.15 — the agent still cannot run shell commands without an explicit prompt, so most of the intended guardrail survives.
opencode run --agent <name> -m opencode/big-pickle "reply with the single word: hi":
bigplain → hi (exit 0)
bigask → hi (exit 0)
bigdeny → Error: OpenCode's free tier can only be used from within OpenCode (exit 1)
bigtoolsoff → same error (exit 1)
plan (built-in) → same error (exit 1)
So it is not shell-* enforcement per se — merely withholding the bash tool from the outgoing request makes the gate reject, exactly as #51315 describes for tools.read. ask/allow keep bash present and work. This also reproduces for a built-in agent (plan with permission.bash: "deny"), no custom frontmatter required.
Workaround:permission.bash: "ask" (the tool is advertised; execution still requires approval), or a paid Go model.
Suggested fix: keep bash in the request schema while denying execution locally, or make the error message state the bash-tool requirement.
Reproduced on Opencode V 1.18.34. Reporting a finding that may help with this and related free-tier/tool-availability issues (looks related to #50081, possibly #51315 and #49723 , #49680 too, though I haven't tested those specifically).
Root cause (as far as I can tell): The free-tier rejection isn't tied to the deny value itself — it's triggered when a tool is removed from the model's tool list entirely.
Denying with a flat tool: deny drops the tool from the list and trips the "can only be used from within OpenCode" error.
Keeping the tool present in the list — even if every actual command/action is denied — avoids it.
Minimal repro (bash):
permission:
edit: allowbash: deny
→ Error from provider (Console): OpenCode's free tier can only be used from within OpenCode
→ Works. Bash tool stays visible to the model, but only ls * is actually permitted — everything else is denied.
Generalizing this: this is probably not bash-specific. My guess is the free tier is checking "is the tool present," not "what can it do," so the same trick should apply to any gated tool: instead of a flat deny, give it a pattern-based permission block with "*": "deny" and one narrow, basically-useless allow rule, just to keep the tool visible without actually handing it any real power.
For bash, ls * works well since it's read-only and side-effect-free. For tools like edit or write, there may not be an equally harmless real carve-out — you may need to scope the allow-rule to a path pattern that can't realistically match anything (e.g. a fixed nonexistent file path) just to satisfy the presence check.
Update: Retested on Desktop-managed service 2.0.22 after updating. Six fresh
build-session requests (two models × three permission configurations) reproduce
the same results: default succeeds, edit deny + shell deny fails with the same
403 FreeTierError, edit deny + shell ask succeeds. No tools were called. The
update did not resolve this reproduction; agent configuration was not changed.
Suggested title if a separate issue is requested: policy: shell deny rejects Spark and MiMo free requests in V2 Desktop 2.0.21
Summary
Confirming this permission-dependent free-tier rejection on OpenCode V2 Desktop
2.0.21 with both MiMo Flash Free and Muse Spark Contributor Free. On the same
Desktop-managed service, changing only the shell permission from default to
deny changes a successful reply into HTTP 403 FreeTierError.
Environment
opencode version: Desktop-bundled CLI and shared service 2.0.21; separate PATH CLI 2.0.18
Install/channel: official installed Desktop; latest release channel reported by runtime diagnostics
Active plugins: @hueyexe/opencode-ensemble@0.18.0 and a local Panel plugin. Not reproduced with plugins disabled. All comparison tests used the built-in build agent, not Panel agents.
Reproduction
Start the official Desktop application and use its managed service.
Create a fresh built-in build session via POST /api/session with an explicit model reference and the active project location. No custom session permissions. Test separately with:
Submit a prompt via POST /api/session/{sessionID}/prompt: Connection test. Do not use tools, read or change files. Reply only OK. (Actual test prompt was the Turkish equivalent, requesting TAMAM.) Both models reply successfully.
Repeat in a fresh session with only this added session permission:
Both models reply successfully again. No shell execution was requested or approved.
Expected Behavior
Denying shell execution must not invalidate legitimate requests from OpenCode
or prevent read-only reviewers from using the free-tier models.
Actual Behavior
Assistant context contains:
{
"type": "provider.auth",
"message": "OpenCode's free tier can only be used from within OpenCode",
"status": 403,
"response": {
"body": "{\"type\":\"error\",\"error\":{\"type\":\"FreeTierError\",\"message\":\"OpenCode's free tier can only be used from within OpenCode\"}}"
}
}
Additional Context
Same Desktop service, built-in build agent
MiMo free
Spark free
Default shell permissions
Successful reply
Successful reply
shell * deny
403 FreeTierError
403 FreeTierError
shell * ask + edit * deny
Successful reply
Successful reply
Custom Panel reviewer definitions deny both edit and shell, which led us to
this issue. An earlier all-tools-denied build test incorrectly suggested a
Desktop identity problem; the controlled same-service tests above ruled that
out as the explanation for this reproduction.
The shared service is launched by official Desktop with OPENCODE_CLIENT=desktop.
No provider headers, credentials or endpoints were modified, and no paid model
was substituted. Independent official CLI tests also succeeded but are not
needed to demonstrate the permission-only comparison above.
The workaround changes a strict shell ban into approval-required execution;
it is not an equivalent read-only security policy. edit deny alone does not
prevent filesystem mutation through approved shell commands. Please fix the
free-tier check rather than requiring users to weaken reviewer permissions.
This is consistent with the request-toolset observations already reported in
this issue. We did not capture outgoing HTTPS bodies or inspect the provider's
server-side validation code, so we are not claiming wire-level proof of the
specific tool-presence check.
The agent never calls bash. Declaring a noop echo allow is enough to route correctly. This matches #52880 and #49723.
Happy to add logs if a maintainer tells me which ones.
Reproduced on v2.0.19 / Windows 10.0.26300 with a second config surface: agents defined inline in opencode.json ("mode": "subagent" with "permission": {"bash": "deny", "edit": "deny", "task": "deny", "write": "deny"}) — no agents/*.md frontmatter involved.
Evidence (4/4 failures in one server run):
Two reviewer-lens task launches (agent: review-reliability, prompt materialized by the gentle-ai review relay) produced child sessions ses_ee2e63886ffe2sDW5dlxGou8QW and ses_ee2e54bb9ffeW6CEssRfn4YpKs that died with:
level=ERROR message="Failed to drain Session"
cause="AI.Error: OpenCode's free tier can only be used from within OpenCode ...
[cause]: AI.Error.Authentication: OpenCode's free tier can only be used from within OpenCode"
The caller surface was opencode_review_transport_relay_refused (reason: provider_failed).
Control probe: gentle-ai-explore (same inline permission block) with the prompt "Reply with exactly one line: PROBE-OK. Do not use any tools." → Subagent failed (sessionID: ses_ee2e02e8fffebzF8Vh9CO87CK9): OpenCode's free tier can only be used from within OpenCode.
The parent session in the same process on the identical free-tier model (opencode/big-pickle) works normally; only child sessions are rejected.
Same log has a precedent from 2026-10-04 (providerID=opencode modelID=big-pickle agent=gentle-ai-explore mode=subagent).
This matches the control matrix in #51193 / #53908 (shell deny → fails, edit deny only → works), but the trigger here is the opencode.json inline permission schema, which the existing reports do not cover.
Cross-posting the datapoint from #54744 (marked duplicate of this issue) so it lives next to the wire captures:
An 8-tool allowlist including bash and read still 403s (OpenCode's free tier can only be used from within OpenCode) on free-tier models in the same session where full-toolset agents pass. So the gateway wants more of the default toolset than those two — consistent with hexxmate's conclusion that removing any gated tool trips the check.
Client-side trace at v2.0.27 (same version): the sentence exists nowhere in the repo tree or the shipped binary (0 matches) — it is server-generated. The client only takes the 401/403 body verbatim (packages/ai/src/route/executor.ts:90-141) and maps it to AI.Error.Authentication (packages/ai/src/provider-error.ts:267-273). Denied tools never reach the wire: Tool.snapshot skips whollyDisabled tools (packages/core/src/tool.ts:225-233,292-295); the body is built from that snapshot (packages/core/src/session/model-request.ts:298).
Concrete fix proposal: the client already sends x-opencode-session-id / x-opencode-parent-session-id (model-request.ts:281-292), which prove the call comes from a real OpenCode session tree. Gating on those instead of on the tools array shape would stop punishing legitimately restricted agents.
Deny-first matrix from #54882, since it may help pin down what the gateway actually requires.
Setup: project config has exactly one permission rule — { "action": "*", "resource": "*", "effect": "deny" } — and the free-model agent is a subagent (mode: subagent, model: opencode/mimo-v2.6-flash-free), so this gate clearly applies to subagents spawned from inside OpenCode too, not only to primary agents. Version v2.0.26, Linux aarch64. Same trivial probe every time ("Reply with exactly PROBE-OK and nothing else. Do not call tools or modify files."), nothing else changed between runs.
The README.md run confirms @opencode-agent's prediction above — the specific resource is irrelevant, and the config file itself does not need to be readable. Any read allow looks sufficient here; a shell allow on its own does not.
This sits awkwardly next to the datapoint cross-posted from console: free-tier 403 rejects restricted subagents even with bash allowed #54744, where an 8-tool allowlist including bash and read still 403s. Here a two-rule allowlist (read + shell) passes. So it is not simply "the gateway wants most of the default tool set" — either tool identity matters more than count, or the intermittency seen in both threads is doing a lot of the work.
The intermittency is real and worth separating from the permission question: with an unchanged allowlist that had passed many times, the same probe then failed 3 times in a row and later passed again. Any fix probably needs to address both the "denied tools are dropped from the request" behaviour and whatever makes an otherwise-eligible request flap.
Empirical results from a real-world repro (Windows 11 Insider build 10.0.26340, opencode v2.0.27, stock desktop install, model opencode/mimo-v2.6-flash-free), re: the deny→ask workaround:
Setup: 9 custom agents in .opencode/agents/. Exactly one of them (planner) had bash: deny in its frontmatter permissions — the other 8 only have edit: deny. Every subagent spawn of planner failed 2/2 with:
OpenCode's free tier can only be used from within OpenCode.
Download it at https://opencode.ai and keep building.
while the parent session on the same model worked normally.
Tests, one change at a time:
bash
edit
spawn result
deny
deny
❌ fails (2/2, exact error above)
ask
ask
✅ spawns + completes
ask
deny
✅ spawns + completes
So in this case bash: deny was the trigger and edit: deny was harmless — consistent with #54744's finding that this isn't purely "any missing standard tool": the shell/bash tool appears to be the one whose absence trips the check most often, but an allowlist-only config can apparently still fail (the workspace-level config here was an allowlist including shell, and it worked fine — the failure only started at the agent-level bash: deny).
Also explains the partial-failure pattern in #49723-style reports: only agents carrying the restrictive rule fail, so some agents work and some don't in the same install.
Workaround confirmed: bash: deny → bash: ask on the offending custom agent. (Kept edit: deny to preserve the read-only intent of that agent — spawn still works.)
Adding one repro path that I don't see covered above: a plugin calling ctx.generate.text with no tools array at all, rather than an agent whose tool list got pruned by deny rules.
The string tools does not appear anywhere in that file. The request body therefore carries an empty tool list, which is the maximal form of the "standard tools absent from the request" condition described by @avaloner1109, @hexxmate and @w3struk — there's no deny rule or allowlist involved at all.
Evidence
Same ctx.generate.text call, same process, same session, differing only in the model:
model
provider
reviews
result
deepseek/deepseek-v4.1-flash
openrouter
380
all succeed
space-bunny-free
opencode
1388 success, 41 error
see below
The openrouter model kept working after the free-tier failures had started — 55 successful generate.text calls on 2026-10-11 through 2026-10-14 — so the free-model 403s are not a general breakage of the generate API or of the session.
Daily breakdown for space-bunny-free, counting only calls that actually reached the model:
Date
Success
Error
10-05
188
0
10-06
169
0
10-07
283
1
10-08
278
1
10-10
231
0
10-11
239
2
10-14
0
29
Sporadic single failures through 10-11, then total failure on 10-14. OpenCode was 2.0.26 on both sides of the boundary and the plugin's last commit was 2026-10-09, before the successes were still landing — so this reads as the gateway's tool-set check tightening rather than a local change.
The error string is identical to the reports above:
Generate.UnavailableError: OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.
Why this case is worth separating out
The documented workarounds in this thread all work by keeping a gated tool present in the list while denying its actions (bash: "*": deny plus a narrow allow, or ask instead of deny). None of them apply here, because there is no permission configuration to adjust — the caller supplies no tools at all. If the check is "does this body look like it came from OpenCode", then an explicitly tool-less generate.text from an in-process plugin is being judged by a heuristic that assumes tool presence implies OpenCode origin, and it will never satisfy it.
Possible question for whoever picks this up: should generate.text from a plugin context be treated as trusted in-process, or should plugin callers be able to declare a tool list so the request still looks like an OpenCode request?
Impact
The affected plugin reviews every permission decision, so with its fail-open onError: "ask" default the session degrades to prompting on every tool call. Running the reviewer on a non-free-tier model avoids it entirely, but the plugin gives no way to choose a different reviewer model.
Environment
opencode 2.0.26
Windows 11, NT 10.0.26200 x64, Windows Terminal, pwsh 7.6.7
Confirming this from a downstream project that depends on restricting an agent's tools headlessly (automemory — a blind summarisation scribe that must not touch the filesystem). Full measured matrix on 1.19.0, macOS.
Minimal trigger
read must be removed from the tools list sent to the server. ask and allow keep it present and work; deny and false remove it and fail.
--standalone makes no difference. Same account, same free tier, same model throughout.
The workaround does not generalise to headless callers
Agent-scoping the deny appears to work, but only when the agent isn't selected:
Config
--agent
result
agent.X.permission = {read: "deny"}
no
OK
same agent, identical config
yes
403 (3/3)
opencode run --agent X makes X the session agent, so X's tools list is what reaches the server. For a headless scribe there is therefore no config-only way to deny read on the free tier. Worth stating plainly in #51241, since "scope it to a subagent" is the obvious next thing anyone will try.
Two points that may narrow scope
bash: "deny" does not reproduce (2/2 OK), while read denies reproducibly do (5/5 across both forms). [BUG] Free models fail when shell or read permissions are denied #51241 lists reador shell — I couldn't get a shell trigger through the bash permission key. Possibly a different key name, or version-specific.
It says "OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai" — which is both the wrong diagnosis (not a tier gate) and self-contradictory advice (you are running OpenCode). It sends users to pay for behaviour that payment doesn't change. Flagging as a docs/message issue independent of the underlying bug.
Confirmed on OpenCode 1.19.2 (official npm opencode-ai@1.19.2), macOS 26.6.2 / arm64, using the built-in default agent and opencode/glm-5.3-flash-free in headless mode.
Reproduction
From the same empty working directory, start the server:
opencode run --attach http://127.0.0.1:4096 \
--model opencode/glm-5.3-flash-free --format json \
'Ответь ровно HEADLESS_OK. Не вызывай инструменты и не изменяй файлы.'
The prompt means: “Reply with exactly HEADLESS_OK. Do not call tools or change files.”
Stop the server, restart it with only the permission value changed, then repeat the same client command in a new session:
HTTP 403 FreeTierError: “OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.”
{"*":"ask"}
HEADLESS_OK, finish: "stop"; no tools called
Same installed version, model, credentials, configuration files, and proxy settings in both cases. The environment override was applied to the server process, which was restarted between cases.
Results were verified through the stored GET /session/:id/message records. In this setup the attached CLI could exit with code 0 even for the rejected request, so exit code alone was insufficient.
This reproduction uses a global tool denial; I did not isolate bash alone.
Thanks for the report. We're tracking this same free-tier rejection when shell access is denied in #49723, so I'm closing this as a duplicate. Please follow that issue for updates.
Activity
avaloner1109 commentedon Sep 23, 2026
Additional evidence — I independently hit this exact trigger and isolated the mechanism. This is the same root cause as #50806 / #49588 (compaction) / #49756, and it is not specific to the permission frontmatter or to shell-* denial specifically: the gate keys off the absence of the
bashtool in the request body.Minimal repro (no frontmatter needed)
opencode.json:{ "agent": { "t2": { "model": "opencode/big-pickle", "tools": { "bash": false } } } }opencode run --agent t2 -m opencode/big-pickle "hi"→ ❌OpenCode's free tier can only be used from within OpenCode"tools": { "bash": false }removed → ✅ worksEnvironment: opencode 1.18.21 and 1.18.32 (repro'd on both, so it is not a version-string gate), Windows, npm global install,
provider=opencode(Console), modelbig-pickle, both TUI and headlessopencode run.Wire-level proof
I pointed the Console provider at a local mock endpoint and captured the exact requests for the working vs failing agent. The two requests are byte-identical in every header —
authorization,user-agent: opencode/1.18.32 ai-sdk/provider-utils/4.0.40 runtime/bun/1.3.14,x-opencode-client,x-opencode-project,x-opencode-session,x-opencode-requestall match. The only difference is the request body:bashbashSame binary, same account, same project, same model — a byte-level diff of the two HTTP requests reduces entirely to the
toolsarray missingbash.Real-world impact
Any agent that denies
bash/shell is fully blocked on the free tier:permission: { "bash": "deny", "interactive_bash": "deny" }for plan-mode-only) fails 100% of requests, while its main agent (bash present) works fine on the same provider/model/machine.Suggested direction
The free-tier origin check should not depend on the request's
toolsarray (any tool-restricted agent, including legitimate read-only/plan agents, gets a false "outside OpenCode" verdict). A signed session/ticket or the existingx-opencode-session/x-opencode-requestheaders validated server-side would be a more robust signal. Happy to provide the full capture if useful.ebzych commentedon Sep 24, 2026
Same in v1.18.25 with
It worked in the past and works with
askandallownow.ruefenacht27 commentedon Sep 29, 2026
Confirming this on V1 as well —
permission:block syntax rather than V2permissions:— plus a bisect that narrows the trigger a bit further.Environment
tools.*overrides in configopencode(Zen Console), free-tier modelsV1 repro
Agent at
~/.config/opencode/agent/example.md:Spawning it via the
subagenttool fails immediately:Same agent, same parent session, same model, same task — the only variable changed between pass and fail is the
bashline.Bisect
Four agents differing only in the
bashline, allmode: subagent, spawned in alternating order from one parent session:bashvalueaskdenyTwo things this adds on top of the V2 repro above:
bashkey entirely also works — so the full permission block minusbashis fine, and no other key in the block is implicated. Only theallow->denytransition trips it.bash: askworks. That suggests the gate may be reacting to tool removal rather than to shell permissions as such.Two scope notes that may help triage
subagenttool path. The same agent selected and used as a primary agent (via the agent picker) works normally and has done for some time. Only the child-session path fails. That also explains why this can go unnoticed for a while — the interactive path is the one people actually use.explorespawned fine for me while both custom agents failed, from the same parent session. That looks like the same shape as subagents: explore agent denied free tier can only be used from within OpenCode while running inside the CLI — general works with identical models #49723 (explore tripping the gate whilegeneralworks), which suggests the trigger is request-shape-dependent rather than agent-dependent.Root cause pointer
#51315 appears to have identified the actual mechanism: the gate runs server-side on Zen and rejects requests whose tools array is missing
reador carries 0-3 tools, and disablingedit/write/apply_patchis harmless. If that's right, the deny is changing the tools array in some way that trips the same check, and the fix belongs in the gate rather than in permission normalization.One suggestion on the error message itself: because it surfaces as
AI.Error.Authenticationand names billing, the natural first debugging step is to check the account and re-login, which is always a dead end here. An error that said "the configured permissions produced a request the free tier rejected" would save a lot of that.Workaround
bash: askinstead ofbash: deny. Confirmed working on 1.18.15 — the agent still cannot run shell commands without an explicit prompt, so most of the intended guardrail survives.Mike-Rice commentedon Sep 30, 2026
Confirming on V1 and built-in agents, plus a CLI-only minimal repro.
Environment: opencode 1.18.33 (V1 plugin API), Linux; built-in
opencodeprovider; free-tier modelopencode/big-pickle; empty scratch project, no third-party plugins.opencode.json:{ "$schema": "https://opencode.ai/config.json", "agent": { "bigplain": { "model": "opencode/big-pickle" }, "bigask": { "model": "opencode/big-pickle", "permission": { "bash": "ask" } }, "bigdeny": { "model": "opencode/big-pickle", "permission": { "bash": "deny" } }, "bigtoolsoff": { "model": "opencode/big-pickle", "tools": { "bash": false } }, "plan": { "model": "opencode/big-pickle", "permission": { "edit": "deny", "bash": "deny" } } } }opencode run --agent <name> -m opencode/big-pickle "reply with the single word: hi":bigplain→hi(exit 0)bigask→hi(exit 0)bigdeny→Error: OpenCode's free tier can only be used from within OpenCode(exit 1)bigtoolsoff→ same error (exit 1)plan(built-in) → same error (exit 1)So it is not shell-* enforcement per se — merely withholding the
bashtool from the outgoing request makes the gate reject, exactly as #51315 describes fortools.read.ask/allowkeepbashpresent and work. This also reproduces for a built-in agent (planwithpermission.bash: "deny"), no custom frontmatter required.Workaround:
permission.bash: "ask"(the tool is advertised; execution still requires approval), or a paid Go model.Suggested fix: keep
bashin the request schema while denying execution locally, or make the error message state the bash-tool requirement.hexxmate commentedon Oct 2, 2026
WORKAROUND for this issue
Reproduced on Opencode V 1.18.34. Reporting a finding that may help with this and related free-tier/tool-availability issues (looks related to #50081, possibly #51315 and #49723 , #49680 too, though I haven't tested those specifically).
Root cause (as far as I can tell): The free-tier rejection isn't tied to the deny value itself — it's triggered when a tool is removed from the model's tool list entirely.
Denying with a flat tool: deny drops the tool from the list and trips the "can only be used from within OpenCode" error.
Keeping the tool present in the list — even if every actual command/action is denied — avoids it.
Minimal repro (bash):
→
Error from provider (Console): OpenCode's free tier can only be used from within OpenCode→ Works. Bash tool stays visible to the model, but only ls * is actually permitted — everything else is denied.
Generalizing this: this is probably not bash-specific. My guess is the free tier is checking "is the tool present," not "what can it do," so the same trick should apply to any gated tool: instead of a flat deny, give it a pattern-based permission block with
"*": "deny"and one narrow, basically-useless allow rule, just to keep the tool visible without actually handing it any real power.For bash,
ls *works well since it's read-only and side-effect-free. For tools like edit or write, there may not be an equally harmless real carve-out — you may need to scope the allow-rule to a path pattern that can't realistically match anything (e.g. a fixed nonexistent file path) just to satisfy the presence check.yturkkan commentedon Oct 2, 2026
Proposed update to #50627
Update: Retested on Desktop-managed service 2.0.22 after updating. Six fresh
build-session requests (two models × three permission configurations) reproduce
the same results: default succeeds, edit deny + shell deny fails with the same
403 FreeTierError, edit deny + shell ask succeeds. No tools were called. The
update did not resolve this reproduction; agent configuration was not changed.
Suggested title if a separate issue is requested:
policy: shell deny rejects Spark and MiMo free requests in V2 Desktop 2.0.21Summary
Confirming this permission-dependent free-tier rejection on OpenCode V2 Desktop
2.0.21 with both MiMo Flash Free and Muse Spark Contributor Free. On the same
Desktop-managed service, changing only the shell permission from default to
deny changes a successful reply into HTTP 403 FreeTierError.
Environment
Reproduction
{"providerID":"opencode","id":"mimo-v2.6-flash-free"}{"providerID":"opencode","id":"muse-spark-1.3-contributor-free"}Connection test. Do not use tools, read or change files. Reply only OK.(Actual test prompt was the Turkish equivalent, requesting TAMAM.) Both models reply successfully.[{"action":"shell","resource":"*","effect":"deny"}][ {"action":"edit","resource":"*","effect":"deny"}, {"action":"shell","resource":"*","effect":"ask"} ]Expected Behavior
Denying shell execution must not invalidate legitimate requests from OpenCode
or prevent read-only reviewers from using the free-tier models.
Actual Behavior
Assistant context contains:
{ "type": "provider.auth", "message": "OpenCode's free tier can only be used from within OpenCode", "status": 403, "response": { "body": "{\"type\":\"error\",\"error\":{\"type\":\"FreeTierError\",\"message\":\"OpenCode's free tier can only be used from within OpenCode\"}}" } }Additional Context
Custom Panel reviewer definitions deny both edit and shell, which led us to
this issue. An earlier all-tools-denied build test incorrectly suggested a
Desktop identity problem; the controlled same-service tests above ruled that
out as the explanation for this reproduction.
The shared service is launched by official Desktop with OPENCODE_CLIENT=desktop.
No provider headers, credentials or endpoints were modified, and no paid model
was substituted. Independent official CLI tests also succeeded but are not
needed to demonstrate the permission-only comparison above.
The workaround changes a strict shell ban into approval-required execution;
it is not an equivalent read-only security policy. edit deny alone does not
prevent filesystem mutation through approved shell commands. Please fix the
free-tier check rather than requiring users to weaken reviewer permissions.
This is consistent with the request-toolset observations already reported in
this issue. We did not capture outgoing HTTPS bodies or inspect the provider's
server-side validation code, so we are not claiming wire-level proof of the
specific tool-presence check.
hymsoft commentedon Oct 3, 2026
Confirming the same trigger on my setup + minimal workaround.
Setup:
mode: subagent) that never needs bash (only reads.md/.typand returns text)Failing frontmatter (repro 3/3):
Error every time via task:
OpenCode's free tier can only be used from within OpenCode
Working frontmatter (repro 3/3 after restart):
The agent never calls bash. Declaring a noop echo allow is enough to route correctly. This matches #52880 and #49723.
Happy to add logs if a maintainer tells me which ones.
oskar089 commentedon Oct 8, 2026
Reproduced on v2.0.19 / Windows 10.0.26300 with a second config surface: agents defined inline in
opencode.json("mode": "subagent"with"permission": {"bash": "deny", "edit": "deny", "task": "deny", "write": "deny"}) — noagents/*.mdfrontmatter involved.Evidence (4/4 failures in one server run):
Two reviewer-lens task launches (
agent: review-reliability, prompt materialized by the gentle-ai review relay) produced child sessionsses_ee2e63886ffe2sDW5dlxGou8QWandses_ee2e54bb9ffeW6CEssRfn4YpKsthat died with:The caller surface was
opencode_review_transport_relay_refused (reason: provider_failed).Control probe:
gentle-ai-explore(same inline permission block) with the prompt "Reply with exactly one line: PROBE-OK. Do not use any tools." →Subagent failed (sessionID: ses_ee2e02e8fffebzF8Vh9CO87CK9): OpenCode's free tier can only be used from within OpenCode.The parent session in the same process on the identical free-tier model (
opencode/big-pickle) works normally; only child sessions are rejected.Same log has a precedent from 2026-10-04 (
providerID=opencode modelID=big-pickle agent=gentle-ai-explore mode=subagent).This matches the control matrix in #51193 / #53908 (
shelldeny → fails,editdeny only → works), but the trigger here is theopencode.jsoninlinepermissionschema, which the existing reports do not cover.w3struk commentedon Oct 12, 2026
Cross-posting the datapoint from #54744 (marked duplicate of this issue) so it lives next to the wire captures:
bashandreadstill 403s (OpenCode's free tier can only be used from within OpenCode) on free-tier models in the same session where full-toolset agents pass. So the gateway wants more of the default toolset than those two — consistent with hexxmate's conclusion that removing any gated tool trips the check.v2.0.27(same version): the sentence exists nowhere in the repo tree or the shipped binary (0 matches) — it is server-generated. The client only takes the 401/403 body verbatim (packages/ai/src/route/executor.ts:90-141) and maps it toAI.Error.Authentication(packages/ai/src/provider-error.ts:267-273). Denied tools never reach the wire:Tool.snapshotskipswhollyDisabledtools (packages/core/src/tool.ts:225-233,292-295); the body is built from that snapshot (packages/core/src/session/model-request.ts:298).x-opencode-session-id/x-opencode-parent-session-id(model-request.ts:281-292), which prove the call comes from a real OpenCode session tree. Gating on those instead of on thetoolsarray shape would stop punishing legitimately restricted agents.gelnuro commentedon Oct 12, 2026
Deny-first matrix from #54882, since it may help pin down what the gateway actually requires.
Setup: project config has exactly one permission rule —
{ "action": "*", "resource": "*", "effect": "deny" }— and the free-model agent is a subagent (mode: subagent,model: opencode/mimo-v2.6-flash-free), so this gate clearly applies to subagents spawned from inside OpenCode too, not only to primary agents. Version v2.0.26, Linux aarch64. Same trivial probe every time ("Reply with exactly PROBE-OK and nothing else. Do not call tools or modify files."), nothing else changed between runs.permissions:shell: "echo 'Hello!'"onlyread: ".opencode/opencode.jsonc"+shell: "git log *"PROBE-OKrepeatedly, some later failuresread: "README.md"+shell: "echo 'Hello!'"PROBE-OKTwo things worth noting:
README.mdrun confirms @opencode-agent's prediction above — the specific resource is irrelevant, and the config file itself does not need to be readable. Anyreadallow looks sufficient here; ashellallow on its own does not.bashandreadstill 403s. Here a two-rule allowlist (read+shell) passes. So it is not simply "the gateway wants most of the default tool set" — either tool identity matters more than count, or the intermittency seen in both threads is doing a lot of the work.The intermittency is real and worth separating from the permission question: with an unchanged allowlist that had passed many times, the same probe then failed 3 times in a row and later passed again. Any fix probably needs to address both the "denied tools are dropped from the request" behaviour and whatever makes an otherwise-eligible request flap.
UnixUserDev commentedon Oct 14, 2026
Empirical results from a real-world repro (Windows 11 Insider build 10.0.26340, opencode v2.0.27, stock desktop install, model
opencode/mimo-v2.6-flash-free), re: the deny→ask workaround:Setup: 9 custom agents in
.opencode/agents/. Exactly one of them (planner) hadbash: denyin its frontmatter permissions — the other 8 only haveedit: deny. Everysubagentspawn ofplannerfailed 2/2 with:while the parent session on the same model worked normally.
Tests, one change at a time:
basheditdenydenyaskaskaskdenySo in this case
bash: denywas the trigger andedit: denywas harmless — consistent with #54744's finding that this isn't purely "any missing standard tool": the shell/bashtool appears to be the one whose absence trips the check most often, but an allowlist-only config can apparently still fail (the workspace-level config here was an allowlist including shell, and it worked fine — the failure only started at the agent-levelbash: deny).Also explains the partial-failure pattern in #49723-style reports: only agents carrying the restrictive rule fail, so some agents work and some don't in the same install.
Workaround confirmed:
bash: deny→bash: askon the offending custom agent. (Keptedit: denyto preserve the read-only intent of that agent — spawn still works.)cobrabr commentedon Oct 15, 2026
Adding one repro path that I don't see covered above: a plugin calling
ctx.generate.textwith notoolsarray at all, rather than an agent whose tool list got pruned by deny rules.Mechanism
The plugin (https://github.com/aSempruch/opencode-auto-mode) issues its LLM reviewer call from a
permission.evaluatehook:The string
toolsdoes not appear anywhere in that file. The request body therefore carries an empty tool list, which is the maximal form of the "standard tools absent from the request" condition described by @avaloner1109, @hexxmate and @w3struk — there's no deny rule or allowlist involved at all.Evidence
Same
ctx.generate.textcall, same process, same session, differing only in the model:deepseek/deepseek-v4.1-flashopenrouterspace-bunny-freeopencodeThe openrouter model kept working after the free-tier failures had started — 55 successful
generate.textcalls on 2026-10-11 through 2026-10-14 — so the free-model 403s are not a general breakage of the generate API or of the session.Daily breakdown for
space-bunny-free, counting only calls that actually reached the model:Sporadic single failures through 10-11, then total failure on 10-14. OpenCode was 2.0.26 on both sides of the boundary and the plugin's last commit was 2026-10-09, before the successes were still landing — so this reads as the gateway's tool-set check tightening rather than a local change.
The error string is identical to the reports above:
Why this case is worth separating out
The documented workarounds in this thread all work by keeping a gated tool present in the list while denying its actions (
bash: "*": denyplus a narrow allow, oraskinstead ofdeny). None of them apply here, because there is no permission configuration to adjust — the caller supplies no tools at all. If the check is "does this body look like it came from OpenCode", then an explicitly tool-lessgenerate.textfrom an in-process plugin is being judged by a heuristic that assumes tool presence implies OpenCode origin, and it will never satisfy it.Possible question for whoever picks this up: should
generate.textfrom a plugin context be treated as trusted in-process, or should plugin callers be able to declare a tool list so the request still looks like an OpenCode request?Impact
The affected plugin reviews every permission decision, so with its fail-open
onError: "ask"default the session degrades to prompting on every tool call. Running the reviewer on a non-free-tier model avoids it entirely, but the plugin gives no way to choose a different reviewer model.Environment
NT 10.0.26200x64, Windows Terminal, pwsh 7.6.7superpowers,@tarquinen/opencode-dcp,@mohak34/opencode-notifier,github:aSempruch/opencode-auto-modespace-bunny-freeon provideropencodehellovalerie commentedon Oct 16, 2026
Confirming this from a downstream project that depends on restricting an agent's tools headlessly (automemory — a blind summarisation scribe that must not touch the filesystem). Full measured matrix on 1.19.0, macOS.
Minimal trigger
readmust be removed from the tools list sent to the server.askandallowkeep it present and work;denyandfalseremove it and fail.--agent?permission: {read: "deny"}permission: {read: "ask"}permission: {read: "allow"}permission: {}permission: {bash: "deny"}permission: {edit,glob,grep,list,bash,task,webfetch,websearch,lsp,skill: "deny"}(everything except read)tools: {bash:false, write:false, edit:false}tools: {read:false, …}--standalonemakes no difference. Same account, same free tier, same model throughout.The workaround does not generalise to headless callers
Agent-scoping the deny appears to work, but only when the agent isn't selected:
--agentagent.X.permission = {read: "deny"}opencode run --agent Xmakes X the session agent, so X's tools list is what reaches the server. For a headless scribe there is therefore no config-only way to denyreadon the free tier. Worth stating plainly in #51241, since "scope it to a subagent" is the obvious next thing anyone will try.Two points that may narrow scope
bash: "deny"does not reproduce (2/2 OK), whilereaddenies reproducibly do (5/5 across both forms). [BUG] Free models fail whenshellorreadpermissions are denied #51241 listsreadorshell— I couldn't get ashelltrigger through thebashpermission key. Possibly a different key name, or version-specific.tools.read: false) is closed as v1, but this reproduces on 1.19.0 and per this thread affects v2 as well — so that closure may be premature.On the error text
It says "OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai" — which is both the wrong diagnosis (not a tier gate) and self-contradictory advice (you are running OpenCode). It sends users to pay for behaviour that payment doesn't change. Flagging as a docs/message issue independent of the underlying bug.
Filed originally as #55653.
Co-Authored-By: opencode/space-bunny-free noreply@opencode.ai
axisrow commentedon Oct 16, 2026
Confirmed on OpenCode 1.19.2 (official npm
opencode-ai@1.19.2), macOS 26.6.2 / arm64, using the built-in default agent andopencode/glm-5.3-flash-freein headless mode.Reproduction
From the same empty working directory, start the server:
OPENCODE_PERMISSION='{"*":"deny"}' opencode serve --hostname 127.0.0.1 --port 4096In another terminal, create a fresh session:
opencode run --attach http://127.0.0.1:4096 \ --model opencode/glm-5.3-flash-free --format json \ 'Ответь ровно HEADLESS_OK. Не вызывай инструменты и не изменяй файлы.'The prompt means: “Reply with exactly HEADLESS_OK. Do not call tools or change files.”
Stop the server, restart it with only the permission value changed, then repeat the same client command in a new session:
OPENCODE_PERMISSION='{"*":"ask"}' opencode serve --hostname 127.0.0.1 --port 4096Observed results
{"*":"deny"}FreeTierError: “OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.”{"*":"ask"}HEADLESS_OK,finish: "stop"; no tools calledSame installed version, model, credentials, configuration files, and proxy settings in both cases. The environment override was applied to the server process, which was restarted between cases.
Results were verified through the stored
GET /session/:id/messagerecords. In this setup the attached CLI could exit with code 0 even for the rejected request, so exit code alone was insufficient.This reproduction uses a global tool denial; I did not isolate
bashalone.docs: say opencode free-tier models refuse the shell-less reviewer
fix: stop template skills stating facts about repository-owned files (#…
neriousy commentedon Oct 20, 2026
Thanks for the report. We're tracking this same free-tier rejection when shell access is denied in #49723, so I'm closing this as a duplicate. Please follow that issue for updates.