Skip to content

session import succeeds but the session is unusable: every continuation gets 'free tier can only be used from within OpenCode' #55476

Open

Description

@FahadBinHussain

what happens

session import accepts a session and reports success, but the imported session can never be continued. every subsequent message fails within ~500ms with:

AI.Error.Authentication: OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building.

both from the TUI and from opencode run --session <id> "...". the error text then lands in the transcript as assistant messages, so the session fills with repeats of it.

the same session continued via opencode run --session <id> --fork "..." works — the fork is created through the normal path, carries the full imported history, and the model resumes context correctly. so the imported history itself is fine; something about how imported sessions come into existence breaks the free-tier auth path.

repro (minimal, fully synthetic)

{
  "info": { "id": "<fresh ses id>", "projectID": "global", "title": "probe", "time": { "created": 0, "updated": 0, "idle": 0, "viewed": 0 }, "cost": 0, "tokens": { "input": 0, "output": 0, "reasoning": 0, "cache": { "read": 0, "write": 0 } }, "outcome": "succeeded", "agent": "build", "model": { "id": "mimo-v2.6-flash-free", "providerID": "opencode" }, "subpath": "Users/Admin", "location": { "directory": "C:/Users/Admin" } },
  "messages": [ { "id": "<fresh msg id>", "time": { "created": 0 }, "type": "user", "text": "hello" } ]
}
opencode session import probe.json          # -> Imported session: ses_...
opencode run --session ses_... "say ok"     # -> identity error, every time
opencode run --session ses_... --fork "say ok"  # -> works

fresh CLI-created sessions and run --session <cli-session-id> resumes work every time, on the same server process, same model, same config. 100% deterministic: imported fails, native works, across ~10 probes.

what was ruled out (each still fails identically)

  • history size: a 51-message / 688KB slice fails the same as a 6661-message / 69MB one
  • content: a synthetic hello fails; original session content fails
  • info fields: fresh minimal info fails; copied info fails
  • model variant: with and without "variant": "default" in info.model
  • message id scheme: random hex ids and native-shaped format(ms, "012x") + 14 alnum ids both fail

db rows of imported vs native sessions are near-identical (session_v2 / session_message; parts=0, events=0 for both). the request is actually sent — an assistant row is written with the model ref, ~460ms round trip — and comes back rejected.

workaround

fork once after import (run --session <imported-id> --fork "<msg>") and continue in the fork; the imported original is disposable. ideal fix: register imported sessions on the same path as native ones, or fail import loudly if the session cannot be used.

environment

Activity

  1. opencode-agent commented on Oct 14, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I couldn't reproduce this on 2.0.23 (Linux x64) against the real Zen free tier with opencode/mimo-v2.6-flash-free:

    • I imported a minimal session shaped like your probe.json: freshly generated ses_/msg_ IDs, times set to 0, agent build, one hello message, with and without subpath. Then I ran opencode run --session <id> "say ok" and got a normal reply. --fork worked too.
    • I also exported a native session, deleted it and re-imported it. Continuing it worked.

    So your setup must be different in some way. These would help us narrow it down:

    1. Your project config (opencode.json/.jsonc) with any secrets removed, especially the capabilities override. Do you also have a provider entry pointing at https://opencode.ai/zen/v1? Several people saw this same error today because of that entry (OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building. #55443, OpenCode's free tier can only be used from within OpenCode. Download it at https://opencode.ai and keep building. #55435).
    2. Does it still happen if you run the import and run --session from an empty directory with no project config, or with --standalone instead of the background service?
    3. How did you generate the ses_/msg_ IDs for probe.json? Could you attach the exact file that fails?
    4. Does it still happen on the latest release (2.0.29)?

    You can reply here or edit the issue. If you edit it, please also leave a comment so we know you're done.

    Related issues, ordered by likelihood:

  2. FahadBinHussain commented on Oct 14, 2026

    @FahadBinHussain
    Author

    thanks for looking. results from my side (windows x64, 2.0.23):

    1. config: no opencode.ai/zen/v1 entry anywhere (0 matches). the provider block is minimal - provider.opencode.models."mimo-v2.6-flash-free".capabilities = {tools: true, input: ["text"], output: ["text"]} (the session: provider image rejection bricks session — generic 400 error, no recovery path #52042 workaround) plus unrelated custom providers. permission is the string form "allow".
    2. empty-directory test: still fails - including when the imported session's own location.directory is the empty dir (a first pass with only the client cwd moved was inconclusive: the session row's directory pointed back at the project). so no project config is involved at all.
    3. probe inline below - the exact file that fails here. ids are prefix + token_hex(6) + 14 alnum; I also tried native-shaped message ids (format(<ms>, "012x") + 14 alnum) - same failure either way.
    4. version: 2.0.23 on a long-running opencode serve --service (~10h uptime, several config rewrites today). haven't tried 2.0.29 yet - restarting the service kills live sessions on this box, so before I do: is there a known change to the import/session-registration path between 2.0.23 and 2.0.29?
    5. --standalone: skipping for now - this box has a documented two-process-on-one-sqlite hazard (a v1 importer incident), so I don't spawn a second opencode against the live db while sessions are running. happy to test with a throwaway data dir if that would help.

    one more data point: fresh run (new session) and run --session <cli-created-id> resumes work every time on the same service, same model, same config - the rejection is strictly imported sessions, ~10/10 deterministic, ~460ms round trip (the request is sent - an assistant row with the model ref gets written, outcome failed).

    json{"info": {"id": "ses_337b1f84e5c1go74AcDxAHZvNV", "projectID": "global", "title": "probe-p6-emptydir", "time": {"created": 1792003000000, "updated": 1792003000000, "idle": 1792003000000, "viewed": 1792003000000}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "outcome": "succeeded", "agent": "build", "model": {"id": "mimo-v2.6-flash-free", "providerID": "opencode"}, "subpath": "tmp/oc-empty", "location": {"directory": "C:\\\\tmp\\\\oc-empty"}}, "messages": [{"id": "msg_87a89ae7a7cdv26eooEcyquOwr", "time": {"created": 1792003000000}, "type": "user", "text": "hello"}]}

  3. opencode-agent commented on Oct 14, 2026

    @opencode-agent
    Contributor

    Thanks, the exact probe file was the missing piece. I can reproduce this now, on 2.0.23 and on the latest release, 2.0.29.

    The trigger is the session ID format, not the history or the other info fields:

    • Importing your probe.json as is (ses_337b1f84e5c1go74AcDxAHZvNV), then opencode run --session <id> "say ok" fails with "OpenCode's free tier can only be used from within OpenCode…". --fork works.
    • The same file with a native-format session ID continues normally. Native IDs are ses_ + 12 hex digits encoding an inverted timestamp + 14 letters/digits, as OpenCode generates them.
    • Any ses_ + random-hex-prefix ID fails every time.

    OpenCode accepts any ID that starts with ses on import, but the free-tier check on the server appears to reject IDs without a valid embedded timestamp. A fork gets a freshly generated native ID, which would explain why forking works. The script below reproduces both cases.

    To answer your question: nothing between 2.0.23 and 2.0.29 changes this, so restarting your service to upgrade won't fix it. Until it's fixed, you can keep forking after import, or generate session IDs the native way (ses_ + (~(ms * 0x1000 + counter)) as 12 hex digits + 14 letters/digits). Exporting a session from OpenCode also gives you a valid ID. Related: #50627 is another case where the free-tier check rejects requests built from inside OpenCode.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Repro for #55476: an imported session whose ID has a random (non-timestamp) prefix
    # is rejected by the OpenCode Zen free tier; the same session with a native ID works.
    # Uses the real Zen free tier (no credentials needed).
    # OC overrides the opencode command (e.g. to run from source).
    set -u
    OC=${OC:-opencode}
    DIR=$(mktemp -d /tmp/opencode/imp.XXXX); cd "$DIR"
    native=$(bun -e '
    const c="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    const v=~(BigInt(Date.now())*0x1000n+1n)
    const t=Array.from({length:6},(_,i)=>Number((v>>BigInt(40-8*i))&0xffn).toString(16).padStart(2,"0")).join("")
    console.log(t+Array.from(crypto.getRandomValues(new Uint8Array(14)),b=>c[b%62]).join(""))')
    rnd() { head -c 64 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 14; }
    probe() { # $1 = session id
    cat > probe.json <<JSON
    {"info":{"id":"$1","projectID":"global","title":"probe","time":{"created":1792003000000,"updated":1792003000000,"idle":1792003000000,"viewed":1792003000000},"cost":0,"tokens":{"input":0,"output":0,"reasoning":0,"cache":{"read":0,"write":0}},"outcome":"succeeded","agent":"build","model":{"id":"mimo-v2.6-flash-free","providerID":"opencode"},"location":{"directory":"$DIR"}},
     "messages":[{"id":"msg_$(openssl rand -hex 6)$(rnd)","time":{"created":1792003000000},"type":"user","text":"hello"}]}
    JSON
      $OC session import probe.json
      echo "-- run --session $1"; timeout 120 $OC run --session "$1" "say ok" 2>&1 | tail -2
      echo "-- run --session $1 --fork"; timeout 120 $OC run --session "$1" --fork "say ok" 2>&1 | tail -2
    }
    echo "== version: $($OC --version)"
    echo "== random-prefix session ID (ses_ + token_hex(6) + 14 alnum)"; probe "ses_$(openssl rand -hex 6)$(rnd)"
    echo "== native-format session ID"; probe "ses_$native"

    Run it: bash repro.sh

  4. olcanaskar commented on Oct 19, 2026

    @olcanaskar

    Adding a data point from #57147 that sharpens this and questions the "fork works" workaround.

    Environment: Desktop app / background service 2.0.32, Windows 11, model opencode/deepseek-v4.1-flash-free, agent plan.

    An imported session (ses_Ddv16i0l55Ptn7tNW6rUg3wL4n — note the ID is not in the native ses_ + 12 hex + 14 alphanumeric form) fails on every prompt with the free-tier error. A brand-new session in the same project, same agent and model, works at the same moment.

    Forking does not rescue it here:

    1. POST /api/session/<imported>/fork {"before": "<first message id>"} -> a fork with 0 messages.
    2. Any prompt in that empty fork still fails, 403 FreeTierError, pre-inference (~1 s, 0 tokens).
    3. Same experiment on a working (native-ID) session: the empty fork works. Tested interleaved seconds apart from the failing one — empty fork of the imported session fails twice, empty fork of the working session succeeds.

    That matches SessionAffinity.get (parentID ?? fork.sessionID ?? id): a fork keeps reporting the fork source's session ID for provider cache routing, so the gateway still sees the imported ID even though the fork's own ID is native.

    So opencode run --session <id> --fork may have worked in the original report for a different reason (the fork path at that time, or a native-ID source). As of 2.0.32 on 2026-10-19, forking an imported session does not avoid the rejection.

  5. FahadBinHussain commented on Oct 20, 2026

    @FahadBinHussain
    Author

    the fork workaround in the report doesn't hold on current builds, and the source explains why.

    packages/core/src/session/affinity.ts (from #51931):

    export const get = (session: Pick<SessionSchema.Info, "id" | "parentID" | "fork">) =>
      session.parentID ?? session.fork?.sessionID ?? session.id

    model-request.ts builds the provider identity headers from that value:

    const affinity = SessionAffinity.get(session)
    ...
    "x-opencode-session-id": session.id,
    "x-session-affinity": affinity,
    "X-Session-Id": affinity,
    "x-opencode-session": affinity,

    so a fork keeps presenting the fork source's id in x-opencode-session, and its own (native) id only goes in x-opencode-session-id. an empty fork of an imported session still carries the imported id, which is what olcanaskar measured on 2.0.32: empty fork of the imported session fails, empty fork of a native session works, seconds apart.

    one more detail on the id itself: the check decodes the timestamp, it isn't a shape check. ses_337b1f84e5c1go74AcDxAHZvNV matches ses_ + 12 hex + 14 alnum, but the prefix decodes to an implausible time and gets rejected. an id minted with the generator in the bot's repro works.

    so the only workaround that survives is re-importing under a freshly minted native id. longer term, session import should mint one when the incoming id doesn't decode, instead of storing it as-is.

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