{"slug":"opencode-qa","title":"opencode-qa","summary":"QA opencode itself, per case: verify the CLI/terminal (opencode run, db, serve, export), prove a specific plugin hook/action/event fired via the SSE event stream, smoke-test the TUI under tmux, and investigate sessions in opencode's SQLite DB by id, title/name, or message text. S","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-23T08:58:12.722995Z","repo":{"url":"https://github.com/code-yeongyu/oh-my-openagent","stars":69391,"forks":5715,"license":null,"updatedAt":"2026-09-24T23:30:44Z"},"bodyHtml":"<hr>\n<h2>name: opencode-qa\ndescription: \"QA opencode itself, per case: verify the CLI/terminal (opencode run, db, serve, export), prove a specific plugin hook/action/event fired via the SSE event stream, smoke-test the TUI under tmux, and investigate sessions in opencode's SQLite DB by id, title/name, or message text. Ships tested helper scripts (each with a --self-test) plus per-domain references. Use whenever someone wants to QA, smoke-test, verify, or debug opencode's CLI, HTTP server, plugin hooks/events, or TUI, or to find/inspect opencode sessions in the database. Triggers: opencode qa, qa opencode, test opencode, verify opencode hook, opencode session db, find opencode session by id/name/text, opencode tui test, opencode server health, opencode event stream.\"</h2>\n<h1>opencode QA</h1>\n<p>QA the opencode coding agent itself. This skill maps each QA need to a tested\nhelper script and a deep reference. Every script ships a <code>--self-test</code> that\nasserts its scenario against the live machine, so the scripts are both the QA\ntools and their own regression checks.</p>\n<p>Verified against opencode v1.17.7 (bun 1.3.12, macOS). Confirm the installed\nversion with <code>opencode --version</code>; the surface is stable but always sanity\ncheck a flag with <code>opencode &lt;cmd&gt; --help</code>.</p>\n<h2>Golden rules (read before running anything)</h2>\n<ul>\n<li>READS of the live DB are safe and intended. Investigating sessions (Case D)\nonly reads <code>~/.local/share/opencode/opencode.db</code>.</li>\n<li>Anything that SPAWNS opencode (serve, run, the TUI) must use an isolated XDG\nsandbox so QA never writes junk sessions into the real DB. The bundled\nscripts already do this; if you run opencode by hand for QA, set\n<code>XDG_DATA_HOME</code> / <code>XDG_CONFIG_HOME</code> / <code>XDG_STATE_HOME</code> / <code>XDG_CACHE_HOME</code> to\ntemp dirs first.</li>\n<li>Global text search over the <code>part</code> table is a multi-GB scan. Always scope it\n(<code>--session</code>, <code>--recent</code>, or <code>--since</code>). The text script refuses an\nunbounded scan on purpose.</li>\n<li>The opencode source repo (<code>packages/opencode</code>) tests itself with <code>bun test</code>\nand CANNOT run tests from the repo root. See <code>references/testing-harness.md</code>.</li>\n</ul>\n<h2>Setup</h2>\n<p>Scripts live next to this file under <code>scripts/</code>. Invoke them from this skill\ndirectory (or with their absolute path):</p>\n<pre><code>cd &lt;this-skill-dir&gt;                        # .agents/skills/opencode-qa\nbash scripts/lib/common.sh --self-check    # confirm the harness + deps\n</code></pre>\n<p><strong>Docker is the default QA surface.</strong> Run QA inside a disposable container that\nhas the latest opencode and a copy of your config, with the host untouched:\n<code>script/agent/qa-docker.sh</code> (see <a href=\"references/docker-qa.md\">references/docker-qa.md</a>).\nThe local scripts below are the fallback for when Docker is unavailable or on\nWindows.</p>\n<p><code>common.sh</code> provides the shared harness (DB path, SQL escaping, isolated XDG\nsandbox, free port, server start/stop, and an EXIT-trap cleanup). It requires\n<code>opencode</code>, <code>sqlite3</code>, <code>curl</code>, <code>jq</code>, and <code>tmux</code> on PATH.</p>\n<h2>Router: pick your case</h2>\n<table>\n<thead>\n<tr>\n<th>You want to...</th>\n<th>Case</th>\n<th>Script</th>\n<th>Reference</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Run opencode non-interactively / check a CLI command</td>\n<td>A</td>\n<td><code>opencode run --format json</code> (inline)</td>\n<td><code>references/cli-commands.md</code></td>\n</tr>\n<tr>\n<td>Find a session by its id</td>\n<td>D</td>\n<td><code>scripts/db-session-by-id.sh &lt;ses_id&gt;</code></td>\n<td><code>references/db-investigation.md</code></td>\n</tr>\n<tr>\n<td>Find sessions by title/name</td>\n<td>D</td>\n<td><code>scripts/db-session-by-name.sh \"&lt;text&gt;\"</code></td>\n<td><code>references/db-investigation.md</code></td>\n</tr>\n<tr>\n<td>Find sessions by message text</td>\n<td>D</td>\n<td><code>scripts/db-session-by-text.sh --recent N \"&lt;text&gt;\"</code></td>\n<td><code>references/db-investigation.md</code></td>\n</tr>\n<tr>\n<td>Export a whole session as JSON</td>\n<td>D</td>\n<td><code>scripts/export-roundtrip.sh &lt;ses_id&gt;</code></td>\n<td><code>references/db-investigation.md</code></td>\n</tr>\n<tr>\n<td>Check the HTTP server / an endpoint</td>\n<td>B</td>\n<td><code>scripts/server-smoke.sh</code></td>\n<td><code>references/server-api.md</code></td>\n</tr>\n<tr>\n<td>Prove a hook / action / event fired</td>\n<td>B</td>\n<td><code>scripts/sse-hook-probe.sh</code></td>\n<td><code>references/events-hooks.md</code></td>\n</tr>\n<tr>\n<td>Prove serve-topology wake runner-split (reproduced/fixed)</td>\n<td>B</td>\n<td><code>scripts/serve-wake-split-probe.sh --expect reproduced\\|fixed --evidence-dir DIR</code> (self-test: <code>--self-test</code>; fake LLM: <code>scripts/lib/fake-openai-server.mjs</code>)</td>\n<td><code>references/events-hooks.md</code></td>\n</tr>\n<tr>\n<td>Smoke-test the TUI</td>\n<td>C</td>\n<td><code>scripts/tui-smoke.sh</code></td>\n<td><code>references/tui-tmux.md</code></td>\n</tr>\n<tr>\n<td>Write/run a test in the opencode source</td>\n<td>-</td>\n<td>(bun test)</td>\n<td><code>references/testing-harness.md</code></td>\n</tr>\n<tr>\n<td>Drive opencode from a Bun/TS script</td>\n<td>-</td>\n<td>(SDK)</td>\n<td><code>references/sdk.md</code></td>\n</tr>\n</tbody>\n</table>\n<h2>Case A: CLI / terminal works</h2>\n<p>The canonical scriptable, non-interactive entry is <code>opencode run</code>. JSON mode\nemits one event per line so you can assert on it.</p>\n<pre><code># stream structured events (types: text, tool_use, step_start, step_finish, reasoning, error)\nopencode run \"list files in src\" --format json\n# run a slash command\nopencode run --command commit\n# resume the last session\nopencode run -c \"continue\"\n# target an already-running server instead of booting one\nopencode run \"explain auth\" --attach http://127.0.0.1:4096 -p \"$OPENCODE_SERVER_PASSWORD\"\n</code></pre>\n<p>Other QA-useful commands: <code>opencode db path</code>, <code>opencode debug paths</code>,\n<code>opencode session list --format json</code>, <code>opencode models --verbose</code>. Full flag\ndetail in <code>references/cli-commands.md</code>.</p>\n<h2>Case B: a specific hook, action, or event</h2>\n<p>opencode publishes lifecycle events over Server-Sent Events at <code>GET /event</code>.\nPlugins observe the same events via the <code>event</code> hook, so seeing an event on the\nwire proves a hook would fire.</p>\n<pre><code># prove the SSE plumbing works (isolated server, asserts server.connected)\nbash scripts/sse-hook-probe.sh --self-test\n\n# watch a REAL server for a specific event while you trigger an action\nbash scripts/sse-hook-probe.sh --attach http://127.0.0.1:4096 \\\n  --password \"$OPENCODE_SERVER_PASSWORD\" --directory \"$PWD\" \\\n  --event message.part.updated --timeout 30\n</code></pre>\n<p>Trigger an action over HTTP (fire-and-forget so the stream is not blocked):</p>\n<pre><code>curl -X POST -u opencode:$OPENCODE_SERVER_PASSWORD -H 'Content-Type: application/json' \\\n  -d '{\"parts\":[{\"type\":\"text\",\"text\":\"say hi\"}]}' \\\n  \"http://127.0.0.1:4096/session/&lt;ses_id&gt;/prompt_async?directory=$PWD\"\n</code></pre>\n<p>A real prompt needs a configured provider, so run the watch-and-trigger pattern\nagainst your real server, not the isolated sandbox. Event-type catalog, the 21\nplugin hook points, and how to load a local plugin: <code>references/events-hooks.md</code>.\nServer start, auth, and routes: <code>references/server-api.md</code>.</p>\n<h2>Case C: the TUI</h2>\n<pre><code>bash scripts/tui-smoke.sh --self-test\n</code></pre>\n<p>This launches the TUI under tmux in an isolated sandbox, confirms it renders\n(<code>capture-pane</code>), confirms <code>send-keys</code> reaches the composer, tears the tmux\nsession down, and verifies the real DB session count is unchanged.</p>\n<p>When TUI visual QA evidence is needed for a PR, follow\n<code>docs/reference/web-terminal-visual-qa.md</code>: render the TUI through the real\nxterm.js web terminal and screenshot it - NEVER the <code>tmux capture-pane</code> frame,\nwhich degrades color and CJK width. From the repository root:</p>\n<pre><code>node script/qa/web-terminal-visual-qa.mjs --title \"OpenCode TUI QA\" \\\n  --command \"opencode\" --input \"{Enter}\" \\\n  --evidence-dir .omo/evidence/&lt;slug&gt;/opencode-web-terminal\n</code></pre>\n<p>This runs a real pty, renders it in xterm.js under Chrome, and writes\n<code>terminal.txt</code>, <code>terminal-ansi.txt</code>, <code>terminal.png</code> (the true-color artifact),\nand <code>metadata.json</code> with a cleanup receipt (<code>--from-file &lt;capture.ansi&gt;</code> replays\na saved raw stream). The isolated <code>scripts/tui-smoke.sh</code> remains the canonical\nOpenCode TUI boot smoke (tmux), separate from this visual evidence.</p>\n<p>Honest verdict: tmux is fine for SMOKE (did it boot, render, accept a key) but\nfragile for asserting conversation output (the TUI is a 60fps full-screen app).\nFor real behavior assertions use Case A (<code>opencode run</code>), Case B (server API +\nSSE), or the TUI control HTTP API (<code>POST /tui/append-prompt</code>,\n<code>POST /tui/submit-prompt</code>, <code>POST /tui/execute-command</code>). Details and the manual\ntmux recipe: <code>references/tui-tmux.md</code>.</p>\n<h2>Case D: investigate sessions in the DB</h2>\n<p>Read-only against the live SQLite DB. The <code>session</code> table is small (title and\nid lookups are instant); message text lives in the multi-GB <code>part</code> table, so\ntext search must be scoped.</p>\n<pre><code># by id\nbash scripts/db-session-by-id.sh ses_3a4ee6335ffedFB8f76BPU1Eb3\n# by title / name (newest first; second arg = limit)\nbash scripts/db-session-by-name.sh \"auth refactor\" 20\n# by message text - scope with --session, --recent N, or --since \"&lt;window&gt;\"\nbash scripts/db-session-by-text.sh --session ses_3a4e... \"ULTRAWORK\"\nbash scripts/db-session-by-text.sh --recent 50 \"permission denied\"\nbash scripts/db-session-by-text.sh --since \"7 days\" --limit 50 \"TODO\"\n# export an entire session as clean JSON\nbash scripts/export-roundtrip.sh ses_3a4e... &gt; session.json\n</code></pre>\n<p>Ad hoc queries: <code>opencode db \"&lt;SQL&gt;\" --format json</code>. Schema, tested query\nshapes with timings, the legacy <code>message</code>/<code>part</code> vs V2 <code>session_message</code>\ndistinction, and the 25 GB caveat: <code>references/db-investigation.md</code>.</p>\n<h2>Scripts index</h2>\n<p>Run any script with <code>--self-test</code> to verify it against the live machine, or\n<code>-h</code> for usage. DB-read scripts are read-only; serve/sse/tui scripts use an\nisolated sandbox and clean up on exit.</p>\n<table>\n<thead>\n<tr>\n<th>Script</th>\n<th>Case</th>\n<th>Self-test asserts</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>scripts/lib/common.sh --self-check</code></td>\n<td>-</td>\n<td>deps present, DB path resolves, SQL escaping, free port, sandbox auto-removed</td>\n</tr>\n<tr>\n<td><code>scripts/db-session-by-id.sh</code></td>\n<td>D</td>\n<td>id round-trips for a real session</td>\n</tr>\n<tr>\n<td><code>scripts/db-session-by-name.sh</code></td>\n<td>D</td>\n<td>a derived title needle returns &gt;=1 row</td>\n</tr>\n<tr>\n<td><code>scripts/db-session-by-text.sh</code></td>\n<td>D</td>\n<td>scoped search hits; unbounded scan refused; bounded search &lt;30s</td>\n</tr>\n<tr>\n<td><code>scripts/export-roundtrip.sh</code></td>\n<td>D</td>\n<td>export stdout is valid JSON and <code>.info.id</code> round-trips</td>\n</tr>\n<tr>\n<td><code>scripts/server-smoke.sh</code></td>\n<td>B</td>\n<td><code>/global/health</code> healthy, <code>/doc</code> &gt;=100 paths, no-auth -&gt; 401</td>\n</tr>\n<tr>\n<td><code>scripts/sse-hook-probe.sh</code></td>\n<td>B</td>\n<td><code>/event</code> opens and delivers <code>server.connected</code></td>\n</tr>\n<tr>\n<td><code>scripts/tui-smoke.sh</code></td>\n<td>C</td>\n<td>TUI renders under tmux, tears down, real DB untouched</td>\n</tr>\n</tbody>\n</table>\n<h2>Risks and caveats</h2>\n<ul>\n<li>25 GB part table: never run an unbounded text scan. Use <code>--session</code>,\n<code>--recent</code>, or <code>--since</code>. A naive <code>JOIN ... WHERE session.time_created &gt;= X</code>\nscans oldest-first and can take ~50s; the scripts use an <code>IN</code>-subquery on the\nnewest sessions (~20ms).</li>\n<li><code>opencode export</code> writes its banner to STDERR; pipe with <code>2&gt;/dev/null</code> before\n<code>jq</code> or you will get a parse error.</li>\n<li>The server enforces auth only when <code>OPENCODE_SERVER_PASSWORD</code> is set;\notherwise it runs unsecured. Authenticated calls use <code>-u opencode:$PASS</code>.\nUnauthenticated calls to a secured server return HTTP 401.</li>\n<li>Installed binary vs dev source: cite dev source paths for internals but\nverify flags against the installed <code>opencode &lt;cmd&gt; --help</code>.</li>\n<li>Isolation: any QA that spawns opencode must use an isolated XDG sandbox so it\nnever pollutes the real DB. Prove it by comparing\n<code>sqlite3 \"$(opencode db path)\" \"SELECT count(*) FROM session\"</code> before and\nafter.</li>\n<li>TUI output assertions are fragile; use the API for real assertions.</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li><code>references/cli-commands.md</code> - every QA-relevant opencode subcommand and flag</li>\n<li><code>references/db-investigation.md</code> - DB schema, tested queries, the 25 GB caveat</li>\n<li><code>references/server-api.md</code> - server start, auth, route catalog, /doc</li>\n<li><code>references/events-hooks.md</code> - SSE endpoints, event types, plugin hooks</li>\n<li><code>references/tui-tmux.md</code> - tmux recipe, isolation, TUI control API</li>\n<li><code>references/testing-harness.md</code> - how opencode tests itself (bun test)</li>\n<li><code>references/sdk.md</code> - the @opencode-ai/sdk client (reference only)</li>\n<li><code>references/docker-qa.md</code> - run QA in a disposable Docker container (default; local is the fallback)</li>\n</ul>\n","files":[{"path":"references/cli-commands.md","sizeBytes":3859,"isText":true},{"path":"references/db-investigation.md","sizeBytes":5935,"isText":true},{"path":"references/docker-qa.md","sizeBytes":3316,"isText":true},{"path":"references/events-hooks.md","sizeBytes":4586,"isText":true},{"path":"references/sdk.md","sizeBytes":4218,"isText":true},{"path":"references/server-api.md","sizeBytes":6645,"isText":true},{"path":"references/testing-harness.md","sizeBytes":6341,"isText":true},{"path":"references/tui-tmux.md","sizeBytes":4074,"isText":true},{"path":"scripts/db-session-by-id.sh","sizeBytes":1760,"isText":true},{"path":"scripts/db-session-by-name.sh","sizeBytes":1991,"isText":true},{"path":"scripts/db-session-by-text.sh","sizeBytes":6709,"isText":true},{"path":"scripts/export-roundtrip.sh","sizeBytes":2865,"isText":true},{"path":"scripts/lib/common.sh","sizeBytes":9749,"isText":true},{"path":"scripts/lib/fake-openai-branches.mjs","sizeBytes":1224,"isText":false},{"path":"scripts/lib/fake-openai-events.mjs","sizeBytes":2369,"isText":false},{"path":"scripts/lib/fake-openai-server.mjs","sizeBytes":3733,"isText":false},{"path":"scripts/lsp-e2e.sh","sizeBytes":137319,"isText":true},{"path":"scripts/server-smoke.sh","sizeBytes":2188,"isText":true},{"path":"scripts/serve-wake-split-probe.sh","sizeBytes":39144,"isText":true},{"path":"scripts/sse-hook-probe.sh","sizeBytes":4498,"isText":true},{"path":"scripts/tui-smoke.sh","sizeBytes":3714,"isText":true},{"path":"SKILL.md","sizeBytes":11537,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"human-reviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"human-reviewed","screen":{"ran":true,"outcome":"flagged-cleared-by-moderator","suspicious":1,"notes":2,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-03T16:37:27.456025Z","sha256":"0DCF0AAA4C5C5AD4081189472B910DD295A6D915EC48C0FDF0302A8AF7DA8896","sizeBytes":80576},"review":null,"source":{"repositoryUrl":"https://github.com/code-yeongyu/oh-my-openagent","path":".agents/skills/opencode-qa","license":null,"commit":"05dcba64b749e7666dcd0296c079d31cf3c298f1","subtreeSha":"DCE3B5AA9CC0A21BB74AD586D0E2F03E57566BF9C973C155E3273D90D1F913AF","lastSyncedAt":"2026-09-25T07:37:33.26442Z"},"reviewedAt":"2026-09-06T18:01:22.701974Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/code-yeongyu/oh-my-openagent/tree/dev/.agents/skills/opencode-qa"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install code-yeongyu-oh-my-openagent@llmmart"},{"target":"git","command":"git clone https://github.com/code-yeongyu/oh-my-openagent.git"}]}