From 657d370a65f9f5b225101faa9a9bb8db29ccdadc Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:51:06 -0700 Subject: [PATCH 1/2] test: discover credential-free OpenCode model --- .github/workflows/v1-e2e.yml | 2 -- tests/e2e.v1-headless.test.ts | 30 +++++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/v1-e2e.yml b/.github/workflows/v1-e2e.yml index 01274d8..d91580a 100644 --- a/.github/workflows/v1-e2e.yml +++ b/.github/workflows/v1-e2e.yml @@ -27,5 +27,3 @@ jobs: - name: Run V1 headless E2E tests run: npm run test:v1:e2e - env: - OPENCODE_E2E_AUTH_JSON: ${{ secrets.OPENCODE_E2E_AUTH_JSON }} diff --git a/tests/e2e.v1-headless.test.ts b/tests/e2e.v1-headless.test.ts index 75ed004..99805db 100644 --- a/tests/e2e.v1-headless.test.ts +++ b/tests/e2e.v1-headless.test.ts @@ -16,19 +16,11 @@ let TEST_XDG_CACHE_HOME = "" let LOG_DIR = "" const LOG_WINDOW_MS = 15 * 60 * 1000 const LOG_FALLBACK_FILES = 3 -const OPENCODE_COMMAND_TIMEOUT_MS = 30_000 +const OPENCODE_COMMAND_TIMEOUT_MS = 90_000 const E2E_ENABLED = process.env.OPENCODE_E2E === "1" -const E2E_MODEL = process.env.OPENCODE_E2E_MODEL ?? "cerebras/zai-glm-4.7" +let E2E_MODEL = process.env.OPENCODE_E2E_MODEL ?? "" function createTestSandbox(): void { - const authJson = process.env.OPENCODE_E2E_AUTH_JSON - if (!authJson) { - throw new Error( - "OPENCODE_E2E_AUTH_JSON is required for the isolated real-host E2E suite." - ) - } - JSON.parse(authJson) - TEST_SANDBOX_DIR = mkdtempSync(join(tmpdir(), "opencode-command-hooks-e2e-")) TEST_CONFIG_DIR = join(TEST_SANDBOX_DIR, "project") TEST_OPENCODE_SUBDIR = join(TEST_CONFIG_DIR, ".opencode") @@ -46,9 +38,6 @@ function createTestSandbox(): void { mkdirSync(TEST_XDG_DATA_HOME, { recursive: true }) mkdirSync(TEST_XDG_CACHE_HOME, { recursive: true }) mkdirSync(LOG_DIR, { recursive: true }) - writeFileSync(join(TEST_XDG_DATA_HOME, "opencode", "auth.json"), authJson, { - mode: 0o600, - }) } function getOpenCodeEnvironment(): Record { @@ -73,6 +62,17 @@ function formatOpenCodeCommand(args: string[]): string { return ["opencode", ...args].map(argument => JSON.stringify(argument)).join(" ") } +function selectFreeOpenCodeModel(output: string): string { + const model = output + .split("\n") + .map(line => line.trim()) + .find(line => line.startsWith("opencode/") && line.endsWith("-free")) + if (!model) { + throw new Error(`OpenCode did not advertise a credential-free model:\n${output}`) + } + return model +} + function formatOpenCodeCommandError( message: string, args: string[], @@ -276,6 +276,10 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { // Verify the executable works in the same isolated environment as the tests. await runOpenCodeCommand(["--version"]) + if (!E2E_MODEL) { + const models = await runOpenCodeCommand(["models", "opencode"]) + E2E_MODEL = selectFreeOpenCodeModel(models.stdout) + } // Enable the plugin in the test opencode config writeTestOpencodeConfig() From 773c4b5a86fe12a485c532fc06be7cfce8c7447a Mon Sep 17 00:00:00 2001 From: Shane Bishop <71288697+shanebishop1@users.noreply.github.com> Date: Fri, 24 Jul 2026 00:58:26 -0700 Subject: [PATCH 2/2] test: prefer responsive free OpenCode models --- tests/e2e.v1-headless.test.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/e2e.v1-headless.test.ts b/tests/e2e.v1-headless.test.ts index 99805db..1582516 100644 --- a/tests/e2e.v1-headless.test.ts +++ b/tests/e2e.v1-headless.test.ts @@ -16,7 +16,7 @@ let TEST_XDG_CACHE_HOME = "" let LOG_DIR = "" const LOG_WINDOW_MS = 15 * 60 * 1000 const LOG_FALLBACK_FILES = 3 -const OPENCODE_COMMAND_TIMEOUT_MS = 90_000 +const OPENCODE_COMMAND_TIMEOUT_MS = 110_000 const E2E_ENABLED = process.env.OPENCODE_E2E === "1" let E2E_MODEL = process.env.OPENCODE_E2E_MODEL ?? "" @@ -63,10 +63,12 @@ function formatOpenCodeCommand(args: string[]): string { } function selectFreeOpenCodeModel(output: string): string { - const model = output + const models = output .split("\n") .map(line => line.trim()) - .find(line => line.startsWith("opencode/") && line.endsWith("-free")) + .filter(line => line.startsWith("opencode/") && line.endsWith("-free")) + const model = ["opencode/mimo-v2.5-free", "opencode/north-mini-code-free"] + .find(candidate => models.includes(candidate)) ?? models[0] if (!model) { throw new Error(`OpenCode did not advertise a credential-free model:\n${output}`) } @@ -298,7 +300,7 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { const hookProofFileName = `e2e-write-hook-${uniqueId}.txt` const hookProofContent = `WRITE_AFTER_HOOK_${uniqueId}` const hookStdout = `WRITE_AFTER_STDOUT_${uniqueId}` - const injectedMarker = `WRITE_AFTER_INJECT_${uniqueId}:${hookStdout}` + const injectedMarker = `Reply only DONE. WRITE_AFTER_INJECT_${uniqueId}:${hookStdout}` const toastMarker = `WRITE_AFTER_TOAST_${uniqueId}` const writtenFilePath = join(TEST_CONFIG_DIR, writtenFileName) const hookProofFilePath = join(TEST_CONFIG_DIR, hookProofFileName) @@ -309,7 +311,7 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { id: `e2e-write-after-${uniqueId}`, when: { phase: "after", tool: "*" }, run: `printf '%s' '${hookProofContent}' > '${hookProofFileName}'; printf '%s' '${hookStdout}'`, - inject: `WRITE_AFTER_INJECT_${uniqueId}:{stdout}`, + inject: `Reply only DONE. WRITE_AFTER_INJECT_${uniqueId}:{stdout}`, toast: { title: "E2E write after-hook", message: toastMarker, @@ -336,5 +338,5 @@ describe.skipIf(!E2E_ENABLED)("V1 headless real-host E2E", () => { } } } - }, 120000) + }, 150000) })