Skip to content

Top-level permission: {"read": "deny"} makes every headless opencode run fail with a misleading free-tier 403 #55653

Closed as duplicate

Description

@hellovalerie

opencode 1.19.0, macOS (darwin). The repro is a single config key.

Supplying an inline config merge that denies top-level read causes every headless
opencode run to fail:

{"permission":{"read":"deny"}}
$ OPENCODE_CONFIG_CONTENT='{"permission":{"read":"deny"}}' \
    opencode run "Reply with exactly: PROBE_OK" --model opencode/space-bunny-free --format json

{"type":"error", ... "error":{"name":"APIError","data":{
  "message":"OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.",
  "statusCode":403, "isRetryable":false, ...}}}

Minimal matrix

Config Result
{"permission":{"read":"deny"}} 403
{"permission":{"read":"allow"}} OK
{"permission":{"read":"ask"}} OK
{"permission":{}} OK
no config at all OK
{"agent":{"x":{"mode":"subagent","permission":{"read":"deny"}}}} OK

Characteristics:

  • Deterministic — reproduced 2/2.
  • Order-independent — interleaved A/B/C over 3 rounds, identical results each round. Not rate limiting.
  • Narrow — sweeping all eleven documented permission keys individually, read is the only one that triggers it. bash, edit, glob, grep, list, task, webfetch, websearch, lsp, skill all pass.
  • Nesting matters — the identical denial scoped inside an agent definition works.

Why this reads as a bug rather than a tier gate

All six probes above ran on the same account, same free tier, same model. Four succeeded. A billing check fails consistently; this one flips on config shape alone.

The practical cost is that the error text is misleading: it points users at upgrading to a paid plan for behaviour that payment does not change. If this is deliberate free-tier policy, the boundary seems arbitrary — read: "deny" behaves differently from read: "ask", which is not a distinction a user would guess at. Either way the error message seems worth revising.

Workaround

Scope the denial to an agent instead of top level:

{"agent":{"my-agent":{"mode":"subagent","permission":{"read":"deny", ...}}}}

That's what I ended up doing, but it required a 3×3 probe matrix to find, which suggests the failure isn't where the message says it is.

Happy to provide logs or a fuller repro script if useful.

Activity

  1. opencode-agent commented on Oct 15, 2026

    @opencode-agent
    Contributor

    Thanks for the very thorough matrix! I think this is a duplicate of #51241. The free-tier check runs on the server, not in OpenCode. It looks at which tools are listed in the model request. A top-level "read": "deny" takes the read tool out of that list, so the server rejects the request as if it came from outside OpenCode. ask keeps the tool in the list, which is why it works. A deny inside a subagent definition doesn't change the main agent's request.

    Possible duplicates and related issues, ordered by likelihood:

    Your finding that the error message points users the wrong way, and your agent-scoped workaround, would be useful on #50627.

    Also, 1.19.0 is OpenCode v1, which only gets critical fixes now. Upgrading to v2 is recommended (opencode upgrade --major), though this free-tier check also affects v2 for now.

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.

  2. hellovalerie commented on Oct 15, 2026

    @hellovalerie
    Author

    Follow-up from a longer investigation — the minimal repro is now exact, and my first report understated it.

    Correction

    The first matrix was misleading. Several of those rows defined the agent in config but never selected it with --agent, so the permission block was never in effect. Selecting the agent changes things.

    Real trigger: read only, regardless of nesting or selection

    Config --agent Result
    permission: {read: "deny"} yes 403
    permission: {read: "deny"} no 403
    permission: {edit,glob,grep,list,bash,task,webfetch,websearch,lsp,skill: "deny"} (everything except read) yes OK
    permission: {bash: "deny"} yes OK
    no permission block yes OK
    tools: {bash:false, write:false, edit:false} yes OK
    tools: {read:false, ...everything} yes 403

    --standalone makes no difference. So: denying read is the trigger. Denying every other tool — including edit and bash — works fine. It is not about nesting, not about top-level vs agent-scoped, and not about subagent mode.

    This is a strange boundary because it is so arbitrary: a user denying edit gets what they asked for, and a user denying read gets a billing error telling them to upgrade.

    Impact on tool-restriction use cases

    This isn't cosmetic for headless/automated callers. Any workflow that restricts an agent's tools has to leave read enabled on the free tier, which is the one capability most likely to leak context into output. Scenarios that restrict everything except read work correctly, so it looks fine until you try to close the last hole.

    Still worth answering

    • Is this intended? If read denial is restricted on the free tier, the error should say so — the current message sends people to buy credits for behaviour that payment doesn't change.
    • Does it reproduce on a paid tier / own-key provider? If not, it should at least say which tier it applies to.

    Filed from an open-source project (automemory) where a blind summarisation agent depends on this; happy to provide a repro script.

  3. opencode-agent commented on Oct 15, 2026

    @opencode-agent
    Contributor

    Thanks for the correction and the clean matrix. The corrected trigger, where denying read fails and denying everything else works, still matches #51241 exactly. That issue's title is "Free models fail when shell or read permissions are denied", and it has confirmations on 1.18.x too. So I still think this is a duplicate of #51241, and #50627 is the broader issue for the same check. Your new data is still useful there: on your setup bash: "deny" passes, while several reporters in #50627 saw it fail. That suggests the set of tools the gateway expects has changed over time.

    On your questions:

    • Is this intended? Nothing from the team says it is. What the earlier threads found is that the free-tier check runs on the OpenCode Zen gateway, not in the app. It looks at which tools are listed in the model request. A deny (or tools.read: false) takes read out of that list, so the gateway treats the request as if it didn't come from OpenCode. That's also why ask works: the tool stays in the list. I agree the error message is misleading for this case. That's worth adding to [BUG] Free models fail when shell or read permissions are denied #51241.
    • Paid tier or your own key? This message comes from the Zen free-tier check, so requests to providers you've configured with your own API key don't go through it. I can't confirm how paid Zen models behave. A data point from you on [BUG] Free models fail when shell or read permissions are denied #51241 would help.

    For your read-restricted summariser, two things to try in the meantime:

    Also, 1.19.0 is OpenCode v1, which only gets critical fixes now. The same gateway check affects v2 as well.

    Unless the new details show a different problem, this issue will still be closed as a duplicate of #51241. Please add your matrix and the automemory use case there, so it stays with the other reports.

  4. hellovalerie commented on Oct 16, 2026

    @hellovalerie
    Author

    Thanks — the mechanism explains my data cleanly, and #51241 / #50627 are indeed the same thing. Posting the matrix to #50627 as suggested.

    One correction to the workaround, since it would mislead anyone headless: agent-scoping the deny does not survive --agent.

    A deny inside a subagent definition doesn't change the main agent's request.

    True for a subagent spawned within a session. But opencode run --agent X makes X the session's agent, so X's tool list is the one sent:

    Config selected via --agent? result
    agent defined with permission: {read: "deny"} no OK
    same agent, same config yes 403 (3/3)

    So for a headless caller there's no agent-scoped escape — if you can't leave read in the tools list, you can't have full blindness on the free tier. That also means the --disallowed-tools story doesn't have a config-only equivalent the way I assumed in the original report.

    Two data points that may narrow it:

    Agreed on the error message: it points at billing for behaviour that payment doesn't change, and "download OpenCode" is actively wrong when you're already running it. That seemed worth a report on its own.

    Co-Authored-By: opencode/space-bunny-free noreply@opencode.ai

  5. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    Thanks for confirming, and for the correction. You're right: my note about subagent definitions only holds for subagents started within a session. With opencode run --agent X, X is the session's agent, so its read: "deny" removes read from the request and the 403 comes back. For headless use, then, the only workarounds are the ones that keep read in the request: a narrow read allow pattern (not yet checked on 1.19.0), or a provider/model that doesn't go through the free-tier check.

    On your two data points:

    Thanks also for raising the error message. Your matrix and the confusing "download OpenCode" wording are useful on #51241 and #50627.

    As agreed, this issue will be closed as a duplicate of #51241.

  6. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    Closing this as a duplicate of #51241, as discussed. That issue tracks the free-tier 403 that happens when read is denied or disabled, and #50627 covers the same server-side check more broadly. Thanks for the detailed matrices and for adding them to #50627. The finding that --agent puts the agent's deny back into effect, and your notes on the misleading error message, will help whoever works on the fix. If you see a case that doesn't fit #51241, please open a new issue with the details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions