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
[BUG] Free models fail when shell or read permissions are denied #51241
The behavior can be reproduced without restarting OpenCode: changing these permissions while OpenCode is running changes whether the next request succeeds or fails.
Expected behavior
Tool permissions should control whether the model can use those tools.
Denying shell or read should not cause a request made from the official OpenCode CLI to be identified as coming from outside OpenCode.
Actual behavior
Disabling these tools causes OpenCode free-tier requests to be rejected as if the request were not originating from OpenCode.
Environment
OpenCode: v2.0.16
Plugins
Reproduced with the official CLI. The behavior is permission-dependent.
OpenCode version
2.0.16
Steps to reproduce
Run OpenCode v2.0.16.
Select a free OpenCode model, for example opencode/big-pickle.
Set shell or read to deny.
Start a new session or send a simple prompt such as Hello.
The request fails with:
Error: OpenCode's free tier can only be used from within OpenCode
changed the title [-]Free models fail when `shell` or `read` permissions are denied[/-][+][BUG] Free models fail when `shell` or `read` permissions are denied[/+]on Sep 25, 2026
I'll take this. Denying shell or read shouldn't trip the free-tier provenance check — those two sounds like they feed the same environment/header path that proves the request came from OpenCode, so a deny probably short-circuits something that looks like client identity to the gateway.
I'll trace where a denied shell/read permission changes what the CLI sends (headers or body) on free-tier requests, fix it so permissions only affect tool availability, and add a regression test that runs with both denied and asserts the request still goes out as an official client.
Update after digging: I couldn't find any client path where a denied shell/read permission changes what the CLI sends — the free-tier provenance check is enforced by the Zen gateway against the config the CLI pulls from the console (plugin/provider/opencode.ts builds its headers from that remote config, independent of tool permissions). I also can't reproduce the gateway's decision locally without a free-tier credential, so I'm stepping back from this one rather than guess at a client fix. Gateway-side investigation (why a denied tool flips provenance) is probably where this lands.
Data point you flagged as unchecked on 1.19.0: the narrow read allow pattern works, and it is a complete workaround. No paid tier needed.
What triggers the 403
read must have at least one surviving allow. Deny-all — in any form — removes the tool from the request and trips the check.
read config
result
"deny"
403
{"*": "deny"}
403
{"*": "deny", "<one path>": "allow"}
OK
"ask"
OK
"allow"
OK
Measured 1.19.0, --standalone, same account/model throughout. Reproducible 5/5.
The deny actually holds
Verified behaviourally, not just by absence of a 403 — with a canary file and a directory listing:
read: {"*":"deny", "/nonexistent-scribe-allowlist/*": "allow"}
→ reading the canary: refused, permission.rejected
→ listing /tmp and /Users: refused, permission.rejected
→ no canary in any output
→ a real dispatch produced a correct record
So the tool stays in the request (satisfying the server-side check) while the agent is genuinely blind. Pointing the surviving allow at a path that never exists gives full blindness with no reachable surface.
One behaviour worth noting
With write/edit also denied, a terse instruction like "output a line" can make the model conclude it cannot "write the record" and refuse, rather than emitting the markdown as chat text. A prompt that says explicitly "output only markdown" avoided this every time. So the two interact: strict blindness makes an ambiguous instruction read as an impossible task, not as an instruction to reply.
Related: #50627, and the original #55653 (now closed as a duplicate of this one).
Re-tested on OpenCode CLI v2.1.2 (Windows 11, signed out, opencode run --standalone, empty project, isolated config dir, model opencode/deepseek-v4.1-flash-free), one setting per run. What has changed since the 1.19.0 results above:
bash/shell/edit deny no longer triggers it on v2.1.2. Only read does.
read still triggers it in every form I tried: "read": "deny", "*": "deny", "tools": { "read": false }, an agent-level read deny (agent.build.permission.read), and a read deny in the project's ./opencode.json with a clean global config.
Config
Result
no config
OK
"permission": { "read": "deny" }
free-tier error
"permission": { "*": "deny" }
free-tier error
"tools": { "read": false }
free-tier error
agent-level read deny
free-tier error
read deny in project ./opencode.json only
free-tier error
"permission": { "read": "ask" }
OK
"permission": { "bash": "deny" } / shell / edit
OK
The project-level case matters: a cloned repo's opencode.json can break free models without any change to your own config. opencode debug config shows which file the rule comes from (it appears as {"action": "read", "resource": "*", "effect": "deny"}).
Fix: "ask" works on the next request. If you need read truly denied, @hellovalerie's narrow-allow pattern above keeps the deny in effect.
Activity
[-]Free models fail when `shell` or `read` permissions are denied[/-][+][BUG] Free models fail when `shell` or `read` permissions are denied[/+]holny commentedon Sep 27, 2026
I'll take this. Denying
shellorreadshouldn't trip the free-tier provenance check — those two sounds like they feed the same environment/header path that proves the request came from OpenCode, so a deny probably short-circuits something that looks like client identity to the gateway.I'll trace where a denied
shell/readpermission changes what the CLI sends (headers or body) on free-tier requests, fix it so permissions only affect tool availability, and add a regression test that runs with both denied and asserts the request still goes out as an official client.Fixes #51241
holny commentedon Sep 27, 2026
Update after digging: I couldn't find any client path where a denied
shell/readpermission changes what the CLI sends — the free-tier provenance check is enforced by the Zen gateway against the config the CLI pulls from the console (plugin/provider/opencode.tsbuilds its headers from that remote config, independent of tool permissions). I also can't reproduce the gateway's decision locally without a free-tier credential, so I'm stepping back from this one rather than guess at a client fix. Gateway-side investigation (why a denied tool flips provenance) is probably where this lands.Mike-Rice commentedon Sep 30, 2026
Confirming on opencode 1.18.33 (V1), built-in
opencodeprovider,opencode/big-pickle, empty project:permission.bash: "deny"→Error: OpenCode's free tier can only be used from within OpenCodepermission.bash: "ask"→ workstools.bash: falsealso reproducesRoot cause is the absent
bashtool in the outgoing request (see #50627 and #51315).ask(orallow) is the workaround.Dioarya commentedon Oct 5, 2026
Repro'd on Windows with
scoop install main/opencode, opencode version 1.18.34, empty project.C:\Users\<User>\.config\opencode\opencode.jsonc
{ "$schema": "https://opencode.ai/config.json", "permission": { "*": "deny" } }and
{ "$schema": "https://opencode.ai/config.json", "permission": "deny" }Otagamo commentedon Oct 10, 2026
Is it possible to escalate this issue to the proper channel?
Two weeks have passed with no attempt at a fix.
hellovalerie commentedon Oct 16, 2026
Data point you flagged as unchecked on 1.19.0: the narrow
readallow pattern works, and it is a complete workaround. No paid tier needed.What triggers the 403
readmust have at least one survivingallow. Deny-all — in any form — removes the tool from the request and trips the check.readconfig"deny"{"*": "deny"}{"*": "deny", "<one path>": "allow"}"ask""allow"Measured 1.19.0,
--standalone, same account/model throughout. Reproducible 5/5.The deny actually holds
Verified behaviourally, not just by absence of a 403 — with a canary file and a directory listing:
So the tool stays in the request (satisfying the server-side check) while the agent is genuinely blind. Pointing the surviving
allowat a path that never exists gives full blindness with no reachable surface.One behaviour worth noting
With
write/editalso denied, a terse instruction like "output a line" can make the model conclude it cannot "write the record" and refuse, rather than emitting the markdown as chat text. A prompt that says explicitly "output only markdown" avoided this every time. So the two interact: strict blindness makes an ambiguous instruction read as an impossible task, not as an instruction to reply.Related: #50627, and the original #55653 (now closed as a duplicate of this one).
Co-Authored-By: opencode/space-bunny-free noreply@opencode.ai
its-tahir commentedon Oct 21, 2026
Re-tested on OpenCode CLI v2.1.2 (Windows 11, signed out,
opencode run --standalone, empty project, isolated config dir, model opencode/deepseek-v4.1-flash-free), one setting per run. What has changed since the 1.19.0 results above:"read": "deny","*": "deny","tools": { "read": false }, an agent-level read deny (agent.build.permission.read), and a read deny in the project's ./opencode.json with a clean global config."permission": { "read": "deny" }"permission": { "*": "deny" }"tools": { "read": false }"permission": { "read": "ask" }"permission": { "bash": "deny" }/shell/editThe project-level case matters: a cloned repo's opencode.json can break free models without any change to your own config.
opencode debug configshows which file the rule comes from (it appears as{"action": "read", "resource": "*", "effect": "deny"}).Fix:
"ask"works on the next request. If you need read truly denied, @hellovalerie's narrow-allow pattern above keeps the deny in effect.Full write-up, including the separate "detected abusive traffic from this IP address" block: https://www.its-tahir.com/blog/opencode-free-tier-can-only-be-used-from-within-opencode