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.
Thanks for the detailed report. I was able to reproduce this on 2.0.16, and it still happens on the latest release, 2.2.0.
What I did, with opencode/big-pickle on the free tier:
With no permission rules, opencode run -m opencode/big-pickle "Say hello in one word" replies normally.
With {"permissions":[{"action":"read","resource":"*","effect":"deny"}]} in opencode.json, the same command fails every time with Free usage limit reached. .... That's a different error text from yours, but it's the same rejection.
Removing the rule makes it work again straight away, so this isn't a real usage limit.
In my runs, denying read alone was enough to trigger it. Denying only shell still worked for me, which differs slightly from what you saw. My guess is that a tool you fully deny is left out of the request, and the free-tier service then treats the request as not coming from OpenCode. That part isn't confirmed yet. Until this is looked at, the workaround is to leave read (and shell) on allow or ask when using the free models.
I couldn't run the check against the latest dev source build, because the free tier rejects source builds even without these rules. The check used the 2.2.0 release instead. The script below reproduces it.
Reproduction script (repro.sh)
#!/usr/bin/env bash# Reproduces #51241: denying `read` makes OpenCode free-tier (Zen) requests fail.# Needs network access to opencode.ai; no credentials (free tier).# Usage: ./repro.sh -> uses `opencode` from PATH (e.g. @opencode/cli@2.0.16)# OC_DEV=/path/to/checkout ./repro.sh -> runs the CLI from sourceset -u
oc() {
if [ -n"${OC_DEV:-}" ];then
bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js""$OC_DEV/packages/cli/src/index.ts""$@"else
opencode "$@"fi
}
export -f oc 2>/dev/null;export HOME=$(mktemp -d)
proj=$(mktemp -d);cd"$proj"; git init -q
oc --version
forrulesin'[]' \
'[{"action":"read","resource":"*","effect":"deny"}]' \
'[{"action":"shell","resource":"*","effect":"deny"},{"action":"read","resource":"*","effect":"deny"}]';doecho"{\"permissions\":$rules}"> opencode.json
echo"== permissions: $rules"
oc run --standalone -m opencode/big-pickle "Say hello in one word"2>&1| tail -1
done
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
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I was able to reproduce this on 2.0.16, and it still happens on the latest release, 2.2.0.
What I did, with
opencode/big-pickleon the free tier:opencode run -m opencode/big-pickle "Say hello in one word"replies normally.{"permissions":[{"action":"read","resource":"*","effect":"deny"}]}inopencode.json, the same command fails every time withFree usage limit reached. .... That's a different error text from yours, but it's the same rejection.In my runs, denying
readalone was enough to trigger it. Denying onlyshellstill worked for me, which differs slightly from what you saw. My guess is that a tool you fully deny is left out of the request, and the free-tier service then treats the request as not coming from OpenCode. That part isn't confirmed yet. Until this is looked at, the workaround is to leaveread(andshell) onalloworaskwhen using the free models.I couldn't run the check against the latest
devsource build, because the free tier rejects source builds even without these rules. The check used the 2.2.0 release instead. The script below reproduces it.Reproduction script (repro.sh)
Run it:
bash repro.sh