From a1e410e4d33c21661693d2c02b813fa678ef5343 Mon Sep 17 00:00:00 2001 From: Darren Hague Date: Thu, 3 Sep 2026 08:58:57 +0100 Subject: [PATCH] fix: propagate OPENCODE_SESSION_ID to wiki tool in-process run (#399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session-tracker plugin's shell.env hook injects OPENCODE_SESSION_ID into every *shell* command's environment, but the wiki tool runs the enchiridion bundle in-process via run() — the hook never fires for it. Fix: in wiki-enchiridion.ts's execute(), inject context.sessionID into process.env.OPENCODE_SESSION_ID before calling run() and restore the prior value in a finally, so save-session (and any other subcommand that reads the session id) sees the correct value. Test: cli.run.test.ts adds a regression test that sets the env var directly (simulating what the plugin now does) and asserts that run(['save-session']) moves past the 'neither ID is set' check before failing on the tracker-state lookup — confirming the env var is read. --- enchiridion-ts/src/cli.run.test.ts | 37 +++++++++++++++++ .../opencode/plugins/wiki-enchiridion.ts | 40 +++++++++++++++---- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/enchiridion-ts/src/cli.run.test.ts b/enchiridion-ts/src/cli.run.test.ts index 0b30b49..1bff296 100644 --- a/enchiridion-ts/src/cli.run.test.ts +++ b/enchiridion-ts/src/cli.run.test.ts @@ -197,3 +197,40 @@ test( } }, ); + +// save-session: OPENCODE_SESSION_ID visible to run() via process.env (#399) +// +// The wiki-enchiridion OpenCode plugin runs the bundle in-process — the +// session-tracker's shell.env hook never fires for it. The plugin therefore +// injects context.sessionID into process.env.OPENCODE_SESSION_ID before +// calling run(). This test simulates the result of that injection by setting +// OPENCODE_SESSION_ID directly in process.env, then calling run(['save-session', +// ...]). We assert that the command moves past the "neither ID is set" check +// (i.e., it reads the env var) before failing on the tracker state check, +// which is the expected failure when no .opencode/wiki-knowledge/sessions/ +// directory exists in cwd's ancestor chain. +test( + "run(['save-session']): reads OPENCODE_SESSION_ID from process.env (not 'neither ID' error)", + { skip: skipReason }, + async () => { + const prevSessionID = process.env.OPENCODE_SESSION_ID; + process.env.OPENCODE_SESSION_ID = "test-opencode-session-id"; + try { + const result = await run(["save-session", "--slug", "test-session"]); + // Must not succeed (no real OpenCode session), but the failure must NOT be + // the "neither $CLAUDE_CODE_SESSION_ID nor $OPENCODE_SESSION_ID" error — + // that error means the env var was invisible, i.e. the bug is present. + assert.notEqual(result.exitCode, 0); + assert.ok( + !result.stderr.includes("Neither $CLAUDE_CODE_SESSION_ID"), + `Expected OPENCODE_SESSION_ID to be read; got: ${result.stderr.trim()}`, + ); + // The expected error is about the tracker state (state not located), not + // about the ID being absent. + assert.match(result.stderr, /OPENCODE_SESSION_ID|session-tracker/); + } finally { + if (prevSessionID === undefined) delete process.env.OPENCODE_SESSION_ID; + else process.env.OPENCODE_SESSION_ID = prevSessionID; + } + }, +); diff --git a/wiki-plugin/wiring/opencode/plugins/wiki-enchiridion.ts b/wiki-plugin/wiring/opencode/plugins/wiki-enchiridion.ts index f882b86..dd274d3 100644 --- a/wiki-plugin/wiring/opencode/plugins/wiki-enchiridion.ts +++ b/wiki-plugin/wiring/opencode/plugins/wiki-enchiridion.ts @@ -18,6 +18,14 @@ * `@opencode-ai/plugin` as a runtime dependency — this plugin imports `tool` * as a value (not just `type Plugin`), and OpenCode runs `bun install` on the * config directory's package.json at startup. + * + * Session-id propagation (#399): OpenCode's `shell.env` hook (session-tracker + * plugin) injects `OPENCODE_SESSION_ID` into every *shell* command's + * environment, but this tool runs the enchiridion bundle in-process, so the + * hook never fires for it. The tool therefore injects `context.sessionID` + * directly into `process.env.OPENCODE_SESSION_ID` before the `run()` call and + * restores the prior value in a `finally`, making `save-session` and any other + * subcommand that reads the session id work correctly. */ import { readFileSync } from "node:fs" import { dirname, join } from "node:path" @@ -78,15 +86,33 @@ export const WikiEnchiridion: Plugin = async ({ directory }) => { const bundle = await resolveBundle(dir) const mod = await import(bundle) // CJS → default-interop namespace const { run } = mod as { run: (argv: string[]) => Promise } - const result = (await run(input.args)) as { - stdout: string - stderr: string - exitCode: number + // Propagate the session id into the in-process environment so that + // `save-session` (and any other subcommand that reads + // OPENCODE_SESSION_ID) can find it. The session-tracker plugin's + // shell.env hook covers *shell* commands; this tool runs the bundle + // in-process and must bridge the gap itself. Restored in a finally so + // a concurrent tool call that set a different value is unaffected. + const prevSessionID = process.env.OPENCODE_SESSION_ID + if (context.sessionID) { + process.env.OPENCODE_SESSION_ID = context.sessionID } - if (result.exitCode !== 0) { - return `enchiridion exited ${result.exitCode}\n${result.stderr.trim()}` + try { + const result = (await run(input.args)) as { + stdout: string + stderr: string + exitCode: number + } + if (result.exitCode !== 0) { + return `enchiridion exited ${result.exitCode}\n${result.stderr.trim()}` + } + return result.stdout + } finally { + if (prevSessionID === undefined) { + delete process.env.OPENCODE_SESSION_ID + } else { + process.env.OPENCODE_SESSION_ID = prevSessionID + } } - return result.stdout }, }), },