This repository is oc, a custom fork of opencode maintained for personal use. It tracks upstream opencode (currently based on the v1.18.27 tag) and is rebased onto new upstream releases as they land. There is no prebuilt binary and no published package: installing opencode from npm/bun gets the stock upstream build with none of these changes. The repo contains everything needed to build the patched version (full source + bun.lock; dist/ and node_modules/ are generated, not stored).
Requires only bun:
git clone https://github.com/malventano/oc && cd oc
bun install
cd packages/opencode
OPENCODE_VERSION=1.18.27-oc bun run script/build.ts --single --skip-install --skip-embed-web-ui
cp dist/opencode-linux-x64/bin/oc /usr/local/bin/oc
bun installat the repo root is REQUIRED, and it applies thepatchedDependencies: the build bundles the patched@opentui/core(patches/@opentui%2Fcore@0.4.5.patch), plus the other patched deps (solid-js, effect, ai-sdk providers, tanstack, etc.). The opentui patch is load-bearing - it carries the render fixes (0196 drawUnstyledText diff setter, 0338 the completion-flip raw sync gated to the void buffer - the white-flash fix on longer writes, 0201 scroll height-delta anchor). A build run with--skip-installWITHOUT a priorbun installwould bundle STOCK opentui and silently lose those fixes.--skip-installis only a speedup for the artifact step; it skips the extra cross-platformbun install @opentui/core@...in build.ts, not the rootbun installthat applies the patch.--skip-embed-web-uiis required: v1.18.5+ app Rollup cannot resolve@opencode-ai/client/promise(upstream dep issue, not ours); plainbun run buildfails on it.- Changing an
@opentuipatch does not re-patch an existing install. Bun keys the patched store on the package version, not the patch content, so after updating an opentui patch you mustrm -rf node_modules/.bun/@opentui+core* node_modules/.bun/@opentui+solid* node_modules/@opentui/core node_modules/@opentui/solid && bun installto force re-apply. Otherwise an old checkout keeps its old store while a fresh clone gets the new one - the classic "works here, fails on a fresh clone" split (2026-09-09 incident: stale hunk offsets mangleparser.worker.js, see bugs/BUG_OPENTUI_PATCH_OFFSETS.md). - Committed
@opentuipatches must have ZERO-offset hunks. Bun applies hunks at the literal declared line numbers, whilegit applycontext-matches and tolerates offsets (reportingHunk #N succeeded at M (offset K)) - sogit apply --checkpassing does NOT mean bun will produce a valid file. Verify withgit apply -v(fail on anyoffset/fuzzline) andnode --check node_modules/.bun/@opentui+core@0.4.5*/node_modules/@opentui/core/parser.worker.jsafterbun install. - Keep the
-ocsuffix inOPENCODE_VERSION: the autoupdate-disable patch (andoc upgrademessage) key off it. Any<tag>-ocversion works;1.18.27-ocmatches this base (the build in this repo is1.18.27-oc-0338). OPENCODE_VERSIONalso pins the channel tolatest, which keeps the session DB at the standard~/.local/share/opencode/opencode.db(shared with stock opencode). Building WITHOUT it puts the branch name in the channel and the DB becomesopencode-<branch>.db(e.g.opencode-main.db): a separate empty database, so no existing sessions appear and new ones land in the wrong file.- The built binary is
dist/opencode-linux-x64/bin/oc(namedoc, unlike upstream'sopencode).
- Built artifact named
oc; CLI rebranded (OC logo,occommand name, oc-specific tips) - Auto-update disabled: custom builds never query the upstream release channel (no npm/GitHub fetch at startup, no auto-replace of the binary);
oc upgradeprints "custom build, update from its source repo" and exits
- Inject-method compaction: the
/compactsummary turn runs as a normal turn with the summary prompt injected as the final user message, keeping the request chain byte-identical to the cached prefix; the summary turn hits ~98% of the KV prefix cache instead of a full miss - Tail retention: the last N full turns are kept verbatim post-compaction (
tail_turns, default 2), with the retention budget no longer clamped (recent turns survive fully) - Reasoning part type preserved on model switch (keeps the prompt byte-identical → cache hit)
- Mode reminders persisted; build-switch gated to the n-1 assistant turn (byte-identical prompts)
- Variant overrides retained per model, shared across agents/modes (set
maxin plan, it staysmaxafter toggling to build); legacy agent-scoped keys migrated on use - Opening an old session no longer restores its stale model/variant; the model resolves to the agent config default
- Variant cycle fixes: explicit-off treated as a real cycle position; current variant passed through manual
/compact - vLLM thinking control via
chat_template_kwargs+ MTP-safe body flatten (excludesmin_p/logit_bias, which speculative decoding rejects) reasoning_tokensfallback for glm45-family parsers that omitcompletion_tokens_details.reasoning_tokensmaxOutputTokens: providerlimit.outputauthoritative, hard 32K cap fallback removed
- Upstream flat-JSON method (0323):
{filePath, oldString, newString, replaceAll}exact string replacement. The hashline (0026-0124) and fence (0124-0322) grammars are retired - they drove 22-63% failure/adherence classes and a V4A contamination collapse (endpoint-measured; docs/EDIT_TOOL_EVALUATION_20260908.md) - Matching ladder (0131, 0301): byte-exact primary; tolerance tiers (line-trimmed, whitespace/indent/escape-normalized, block-anchor, context-aware) fall back; non-byte-exact applies echo the matched tier + line, and NEW indentation rebases onto the file region
- Fail-loud, fail-cheap: unique
oldStringrequired (multiple matches are named), disproportionate matches refused, emptyoldStringrejected on existing files, no partial writes - Read-before-edit enforced; minimal-anchor + region-read guidance in the tool description (0335)
- Time context (0027, 0119): every user message and tool output carries a UTC
<system-reminder>stamp (ISO 8601, second precision, trailing Z - TZ-invariant since 0119); the env block declares the user's local date + offset (Today's date: Wed Aug 13 2026 (America/Kentucky/Monticello, UTC-04:00), epoch-delta'd on change); sessions-browse/skill-metadata emit UTC Z seconds - Loop guard (0028): dual-channel loop detection: repeated identical tool output or repeated user prompts abort with a diagnostic instead of looping
- Sessions DB tools (0030): read-only session browsing, SQLite queries, and confirm-gated write management against the session database
- Squash output (0031): replace a finished tool output with a short summary you write (depth-gated), keeping the context chain small
- Skill metadata (0034, 0038): frontmatter name/description, line/char counts, sibling inventory, mtime, and description byte count for one or all skills; reads raw SKILL.md frontmatter; built-in skills guarded
- tmux pane management: run/poll/keys/capture/wait lifecycle ops for long-running jobs in visible panes
- Shell safety guards (0036, 0040, 0041):
pkill -fandkill -9/-KILLon$$/$PPIDare blocked in the bash tool (anchored to command position; covers semicolon-suffixed and sudo forms) because they hang the session - Bash file-op guard plugin (see plugins/README.md): the ONE non-bundled piece - a hook plugin (
bash-file-op-guard.js) that nudges toward the native file tools and hard-rejects therg -rn/-r n(--replace n) foot-gun. Install it (copy + configpluginentry + restart) or the guard is silently absent.
- Tool prompts: question/read/write/webfetch ship refined guidance (exact JSON array shapes for the question tool, URL discipline for webfetch, output-budget chunking for write)
default.txtships time-awareness and output-efficiency guidance; the shell prompt warns to neverkill -9 $$/$PPID;write.txtdocuments that heredocs/redirections bypass Read-before-Write and the same-turn read cache- The edit description opens with a type-first requirement for every op
- Subagent costs aggregated into the sidebar spent total (with placeholder-session-ID guard)
- Message pruning on prompt submit instead of during streaming (no viewport jumps while scrolled up)
- Session directory filter: non-git sessions scoped by directory, not hierarchical path
- Footbar: session-cwd directory label (home
~-abbreviated), session title, cost DFS, path-first order - Adaptive refresh cadence + reduced render lag
- Footbar spacing/order polish
- Footbar overflow fixed on narrow terminals (0032)
- Session resume: agent restore no longer races the agent-list load (0035)
Sessions are scoped to the directory they were launched in: oc shows a session only when the current working directory matches its stored launch directory (git projects match hierarchically by path relative to the worktree; non-git sessions match by exact directory, the behavior of our session-directory-filter work, PR #31210 / patch 0004). Sessions launched in a different directory do not appear in the list.
The stored directory can also diverge from the original launch path: when a session's directory becomes part of a git repo (or a project is initialized there), the session row can be reassigned to that project, so it stops showing under the original context. If sessions are missing, restore the stored paths to the original launch directories.
The session database is SQLite at ~/.local/share/opencode/opencode.db. Inspect:
sqlite3 ~/.local/share/opencode/opencode.db \
"SELECT id, title, directory, path, project_id FROM session ORDER BY time_updated DESC;"
Restore a session to its original launch directory:
sqlite3 ~/.local/share/opencode/opencode.db \
"UPDATE session SET directory = '/original/launch/dir', path = substr('/original/launch/dir', 2), project_id = 'global' WHERE id = '<session-id>';"
-
pathmirrorsdirectory: for non-git sessions it is the launch directory relative to/with the leading slash stripped (e.g.directory = /home/user/proj→path = home/user/proj). This is the value the session filter matches against, so it must stay in sync withdirectory; do NOT set it to NULL (NULL-path sessions never match the TUI filter) -
project_idis'global'for non-git directories. If a session was reassigned into a git project, restore it. Sessions genuinely launched inside a git repo keep their project-specificproject_idand worktree-relativepath; resetting those hides them from the repo's session list -
If a whole tree moved (renamed parent), rewrite both columns in bulk:
sqlite3 ~/.local/share/opencode/opencode.db
"UPDATE session SET directory = REPLACE(directory, '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/old/parent', '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/new/parent'), path = REPLACE(path, 'old/parent', 'new/parent') WHERE directory LIKE '/old/parent%';"
oc does not need to be stopped: you can direct oc to run these queries itself (the session database is live SQLite and the session list re-reads it). If a session is currently open in the TUI, reopen it to refresh its view.
Optional: redo every session in the DB (regenerates the filter path from each session's stored directory, undoing any stale or munged path values):
sqlite3 ~/.local/share/opencode/opencode.db \
"UPDATE session SET path = substr(directory, 2) WHERE project_id = 'global' AND directory != '/';"
This applies to all non-git sessions (project global): their path is the launch directory relative to / (leading slash stripped, e.g. directory = /home/user/proj becomes path = home/user/proj), which is what the session filter matches against. Run it after correcting directory values. It is also the backfill for legacy rows: sessions created before opencode 2026-04-28 (migration add_session_path added the column without backfill) have path = NULL and never match the filter; this command fills them in. Idempotent: already-correct paths are rewritten to the same value. Sessions started inside a git repo are intentionally NOT touched: they carry a project-specific project_id and a worktree-relative path, and resetting either would hide them from the repo's session list. Verify with:
sqlite3 ~/.local/share/opencode/opencode.db \
"SELECT path, COUNT(*) FROM session WHERE project_id = 'global' GROUP BY path ORDER BY COUNT(*) DESC LIMIT 10;"
The oc binary ships the full tooling: hashline editing, sessions DB tools, time context, loop guard, squash output, skill metadata, tmux pane management, and shell safety guards are compiled in. Nothing needs to be copied, configured, or installed.
ONE exception: the bash file-op guard is a hook plugin, not bundled (see plugins/README.md) - copy plugins/bash-file-op-guard.js into your opencode plugin directory, declare it in the config plugin array, and restart opencode.
This repo is a lean snapshot of the upstream opencode release (upstream-specific CI, dogfood config, community docs, and deploy tooling are stripped) plus the oc patch series. lean-base.sh regenerates the snapshot from a new upstream tag; main is a derived artifact and force-pushes are the normal update path.