One-command fix for OpenCode sessions that die forever with Error: Invalid request parameters.
Two different causes are covered:
| # | Cause | Symptom | Fix in this repo |
|---|---|---|---|
| 1 | NUL bytes (\x00) in conversation history |
400 + "String must not contain null bytes" — every later message in that session fails |
nul-sanitizer plugin strips them before every model call |
| 2 | Removed / deprecated model | 410 + "Model X has been deprecated." |
plugin auto-switches the session to your configured default model and retries |
Both of these are sticky: once they happen, the session is unusable and "continue" does not help, because the bad state is stored in the session history.
curl -fsSL https://raw.githubusercontent.com/CanArslanDev/opencode-invalid-request-fix/main/install.sh | bashgit clone https://github.com/CanArslanDev/opencode-invalid-request-fix.git
cd opencode-invalid-request-fix
./install.shInstalling is idempotent. Existing files are backed up first (*.bak.<timestamp>).
./install.sh --model venice/deepseek-v4-1-flash-uncensored # also set a default model
./install.sh --no-agents # skip the AGENTS.md guidance block
./install.sh --dry-run # show what would happen
./install.sh --prefix ~/.config/opencode # custom config dir| Path | Purpose |
|---|---|
<config>/plugins/nul-sanitizer.ts |
The fix. Hooks prompt, context, compaction, generate, title and retry. |
<config>/scripts/fix-nul-bytes.py |
Permanent DB cleanup for sessions that already contain NULs. |
<config>/AGENTS.md |
A managed block telling the agent to filter binary output at the source. |
<config>/nul-sanitizer.log |
Runtime diagnostics (created on first use; silence with OPENCODE_NUL_QUIET=1). |
<config> is ${XDG_CONFIG_HOME:-$HOME/.config}/opencode.
The plugin also registers a TUI command:
/fix-nuls # permanently clean NULs from the session database (takes a backup first)
/fix-nuls dry # report only, change nothing
You do not need it for the fix to work — the plugin sanitizes on the fly. It is only for housekeeping so stored history stays small and clean.
The plugin runs inside the OpenCode server and hooks the moment right before a request is
dispatched. It walks event.messages and event.system and replaces any \u0000 with a space.
Persisted history is not rewritten, so old broken sessions become usable again too.
await ctx.session.hook("context", onRequest) // agent loop
await ctx.session.hook("compaction", onRequest)
await ctx.session.hook("generate", onRequest)
await ctx.session.hook("title", onRequest)
await ctx.session.hook("retry", onRetry) // 400 → retry, 410 → switch model + retrySee docs/how-it-works.md for the full mechanism, including the root
cause analysis and a reproducible controlled experiment.
ocf-doctor # or ./doctor.sh
# / or:
opencode plugin list # "nul-sanitizer" must be listed
python3 ~/.config/opencode/scripts/fix-nul-bytes.py --dry-run
tail ~/.config/opencode/nul-sanitizer.log # what the plugin actually didExpected: the plugin is listed and no NUL bytes are reported.
Controlled experiment on a real install — one session, one injected NUL, the plugin as the only variable, against a provider that rejects NUL bytes:
plugin OFF + NUL in history -> finish=error provider.invalid-request (the bug)
plugin ON + same NUL -> finish=stop + log: "scrubbed NUL bytes from 1 string(s)"
For the removed-model path: a session pinned to a delisted model
(opencode/exo-free) is switched to the configured fallback and completes normally.
./uninstall.sh
# or, from anywhere:
curl -fsSL https://raw.githubusercontent.com/CanArslanDev/opencode-invalid-request-fix/main/uninstall.sh | bashRemoves the plugin, the script and the managed AGENTS.md block. Your own AGENTS.md content and
all session data are left untouched.
- macOS or Linux,
bash,curl,python3 - OpenCode v2 (plugin API
@opencode/plugin) - No network at run time; everything is local
- The plugin only fixes the outgoing request. Your database may still contain NULs — harmless,
and
/fix-nulscleans them if you care. - If OpenCode ships a breaking plugin-API change, re-check with
opencode plugin listafter upgrading. Seedocs/troubleshooting.md. - The deprecated-model recovery needs a usable default model. Keep
modelinopencode.jsonpointing at something that exists, or setOPENCODE_NUL_FALLBACK_MODEL=provider/model. - It does not help with unrelated 400s (oversized payloads, provider outages).
Root cause found while debugging a real Ableton Live cracking session that kept dying on
grep -a over binary files. Reverse-engineered from that incident.