From c155e933d2bbd1173fbb070036dc963e6cdbe7d9 Mon Sep 17 00:00:00 2001 From: wstczyw <349750996@qq.com> Date: Sat, 19 Sep 2026 09:57:50 +0800 Subject: [PATCH] fix(setup): tolerate unavailable app-server during state probe --- scripts/claude-companion.mjs | 30 +++++++++++++----- tests/integration/claude-companion.test.mjs | 35 +++++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/scripts/claude-companion.mjs b/scripts/claude-companion.mjs index 2a16c9e..14dab79 100644 --- a/scripts/claude-companion.mjs +++ b/scripts/claude-companion.mjs @@ -624,6 +624,15 @@ function buildSetupReport( // setup // --------------------------------------------------------------------------- +function isCodexAppServerUnavailable(error) { + const message = error instanceof Error ? error.message : String(error); + return ( + /Failed to start [^:]+:/i.test(message) || + /app-server exited before responding/i.test(message) || + /app-server timed out waiting/i.test(message) + ); +} + async function handleSetup(argv) { const { options } = parseCommandInput(argv, { valueOptions: ["cwd"], @@ -657,6 +666,7 @@ async function handleSetup(argv) { let pluginStateDetail = `plugin data roots verified: ${pluginDataRoots.join(", ")}`; let pluginStateNextStep = null; let pluginConfig = null; + let pluginRootProbeError = null; const recordPluginStateFailure = (error) => { pluginStateReady = false; pluginStateDetail = `unable to access plugin state: ${ @@ -668,14 +678,11 @@ async function handleSetup(argv) { try { writableRootChanged = await ensureCodexWritableRoots(cwd, pluginDataRoots); } catch (error) { - pluginStateReady = false; - pluginStateDetail = `unable to configure plugin data roots: ${ - error instanceof Error ? error.message : String(error) - }`; - pluginStateNextStep = - `Allow ${pluginDataRoots.join(", ")} under ` + - "`sandbox_workspace_write.writable_roots` in `~/.codex/config.toml`, " + - "restart Codex, then rerun `$cc:setup`."; + if (isCodexAppServerUnavailable(error)) { + pluginRootProbeError = error; + } else { + throw error; + } } if (writableRootChanged) { pluginStateReady = false; @@ -693,6 +700,13 @@ async function handleSetup(argv) { try { ensureStateDir(workspaceRoot); pluginConfig = getConfig(workspaceRoot); + if (pluginRootProbeError) { + pluginStateDetail = + "plugin data roots app-server check unavailable; direct plugin state access verified: " + + (pluginRootProbeError instanceof Error + ? pluginRootProbeError.message + : String(pluginRootProbeError)); + } } catch (error) { recordPluginStateFailure(error); } diff --git a/tests/integration/claude-companion.test.mjs b/tests/integration/claude-companion.test.mjs index 5e808ba..51c0ed8 100644 --- a/tests/integration/claude-companion.test.mjs +++ b/tests/integration/claude-companion.test.mjs @@ -721,6 +721,9 @@ describe("claude-companion integration", () => { it("setup reports plugin-state repair guidance when the app-server is unavailable", () => { const testEnv = createTestEnvironment(); + const codexHome = path.join(testEnv.homeDir, ".codex"); + fs.mkdirSync(codexHome, { recursive: true }); + fs.writeFileSync(path.join(codexHome, "plugins"), "", "utf8"); try { const report = runCompanionJson( @@ -739,16 +742,44 @@ describe("claude-companion integration", () => { assert.equal(report.ready, false); assert.equal(report.reviewGateEnabled, null); assert.equal(report.pluginState.ready, false); - assert.match(report.pluginState.detail, /unable to configure/i); + assert.match(report.pluginState.detail, /unable to access/i); assert.match( report.nextSteps.join("\n"), - /sandbox_workspace_write\.writable_roots/ + /Restart Codex.*rerun `\$cc:setup`/ ); } finally { cleanupTestEnvironment(testEnv); } }); + it("setup remains ready when the app-server reports spawn EPERM but plugin state is writable", () => { + const testEnv = createTestEnvironment(); + const failingServer = path.join(testEnv.rootDir, "spawn-eperm-app-server.mjs"); + fs.writeFileSync( + failingServer, + 'process.stderr.write("spawn EPERM\\n"); process.exitCode = 1;\\n', + "utf8" + ); + + try { + const report = runCompanionJson( + ["setup", "--cwd", testEnv.workspaceDir, "--json"], + { + env: { + ...testEnv.env, + CC_PLUGIN_CODEX_EXECUTABLE: process.execPath, + CC_PLUGIN_CODEX_APP_SERVER_ARGS_JSON: JSON.stringify([failingServer]), + }, + } + ); + + assert.equal(report.pluginState.ready, true); + assert.match(report.pluginState.detail, /direct plugin state access verified/i); + } finally { + cleanupTestEnvironment(testEnv); + } + }); + it("setup trusts current native plugin hooks through Codex hooks/list", () => { const testEnv = createTestEnvironment(); const sourcePath = path.join(PROJECT_ROOT, "hooks", "hooks.json");