- commit
- 0516ea9
- parent
- cc93ae7
- author
- im_wower
- date
- 2026-03-29 01:35:17 +0800 CST
fix: stabilize conductor local api test snapshots
3 files changed,
+44,
-15
1@@ -204,6 +204,9 @@ async function createLocalApiFixture(options = {}) {
2 });
3
4 const snapshot = {
5+ claudeCoded: {
6+ localApiBase: null
7+ },
8 codexd: {
9 localApiBase: null
10 },
11@@ -2595,6 +2598,9 @@ test("parseConductorCliRequest rejects unlisted or non-Tailscale local API hosts
12
13 test("handleConductorHttpRequest keeps degraded runtimes observable but not ready", async () => {
14 const snapshot = {
15+ claudeCoded: {
16+ localApiBase: null
17+ },
18 codexd: {
19 localApiBase: null
20 },
21@@ -4941,6 +4947,9 @@ test("persistent live ingest survives restart and /v1/browser restores recent hi
22 restartedControlPlane.repository,
23 "local-shared-token",
24 {
25+ claudeCoded: {
26+ localApiBase: null
27+ },
28 codexd: {
29 localApiBase: null
30 },
+19,
-11
1@@ -677,6 +677,14 @@ function normalizeOptionalString(value: string | null | undefined): string | nul
2 return normalized === "" ? null : normalized;
3 }
4
5+function getSnapshotClaudeCodedLocalApiBase(snapshot: ConductorRuntimeApiSnapshot): string | null {
6+ return normalizeOptionalString(snapshot.claudeCoded?.localApiBase) ?? null;
7+}
8+
9+function getSnapshotCodexdLocalApiBase(snapshot: ConductorRuntimeApiSnapshot): string | null {
10+ return normalizeOptionalString(snapshot.codexd?.localApiBase) ?? null;
11+}
12+
13 function buildSuccessEnvelope(
14 requestId: string,
15 status: number,
16@@ -3286,7 +3294,7 @@ function buildCodexRouteCatalog(): JsonObject[] {
17 }
18
19 function buildCodexProxyNotes(snapshot: ConductorRuntimeApiSnapshot): string[] {
20- if (snapshot.codexd.localApiBase == null) {
21+ if (getSnapshotCodexdLocalApiBase(snapshot) == null) {
22 return [
23 "Codex routes stay unavailable until independent codexd is configured.",
24 `Set ${CODEXD_LOCAL_API_ENV} to the codexd local HTTP base URL before using /v1/codex.`
25@@ -3300,13 +3308,14 @@ function buildCodexProxyNotes(snapshot: ConductorRuntimeApiSnapshot): string[] {
26 }
27
28 function buildCodexProxyData(snapshot: ConductorRuntimeApiSnapshot): JsonObject {
29+ const codexdLocalApiBase = getSnapshotCodexdLocalApiBase(snapshot);
30 return {
31 auth_mode: "local_network_only",
32 backend: "independent_codexd",
33- enabled: snapshot.codexd.localApiBase != null,
34+ enabled: codexdLocalApiBase != null,
35 route_prefix: "/v1/codex",
36 routes: buildCodexRouteCatalog(),
37- target_base_url: snapshot.codexd.localApiBase,
38+ target_base_url: codexdLocalApiBase,
39 transport: "local_http",
40 notes: buildCodexProxyNotes(snapshot)
41 };
42@@ -3379,7 +3388,7 @@ async function requestCodexd(
43 }
44 ): Promise<{ data: JsonValue; status: number }> {
45 const codexdLocalApiBase =
46- normalizeOptionalString(context.codexdLocalApiBase) ?? context.snapshotLoader().codexd.localApiBase;
47+ normalizeOptionalString(context.codexdLocalApiBase) ?? getSnapshotCodexdLocalApiBase(context.snapshotLoader());
48
49 if (codexdLocalApiBase == null) {
50 throw new LocalApiHttpError(
51@@ -5727,10 +5736,7 @@ async function handleCodexStatusRead(context: LocalApiRequestContext): Promise<C
52 return buildSuccessEnvelope(
53 context.requestId,
54 200,
55- buildCodexStatusData(
56- result.data,
57- normalizeOptionalString(context.codexdLocalApiBase) ?? context.snapshotLoader().codexd.localApiBase ?? ""
58- )
59+ buildCodexStatusData(result.data, normalizeOptionalString(context.codexdLocalApiBase) ?? getSnapshotCodexdLocalApiBase(context.snapshotLoader()) ?? "")
60 );
61 }
62
63@@ -5806,7 +5812,8 @@ async function requestClaudeCoded(
64 }
65 ): Promise<{ data: JsonValue; status: number }> {
66 const claudeCodedLocalApiBase =
67- normalizeOptionalString(context.claudeCodedLocalApiBase) ?? context.snapshotLoader().claudeCoded.localApiBase;
68+ normalizeOptionalString(context.claudeCodedLocalApiBase) ??
69+ getSnapshotClaudeCodedLocalApiBase(context.snapshotLoader());
70
71 if (claudeCodedLocalApiBase == null) {
72 throw new LocalApiHttpError(
73@@ -6465,9 +6472,10 @@ export async function handleConductorHttpRequest(
74 browserRequestPolicy: context.browserRequestPolicy ?? null,
75 browserStateLoader: context.browserStateLoader ?? (() => null),
76 claudeCodedLocalApiBase:
77- normalizeOptionalString(context.claudeCodedLocalApiBase) ?? context.snapshotLoader().claudeCoded.localApiBase,
78+ normalizeOptionalString(context.claudeCodedLocalApiBase) ??
79+ getSnapshotClaudeCodedLocalApiBase(context.snapshotLoader()),
80 codexdLocalApiBase:
81- normalizeOptionalString(context.codexdLocalApiBase) ?? context.snapshotLoader().codexd.localApiBase,
82+ normalizeOptionalString(context.codexdLocalApiBase) ?? getSnapshotCodexdLocalApiBase(context.snapshotLoader()),
83 fetchImpl: context.fetchImpl ?? globalThis.fetch,
84 now: context.now ?? (() => Math.floor(Date.now() / 1000)),
85 params: matchedRoute.params,
+16,
-4
1@@ -2,7 +2,7 @@
2
3 ## 状态
4
5-- 当前状态:`待开始`
6+- 当前状态:`已完成`
7 - 规模预估:`S`
8 - 依赖任务:无
9 - 建议执行者:`Claude`(需要理解 conductor-daemon 测试初始化流程,定位 config 注入缺失)
10@@ -101,21 +101,33 @@
11
12 ### 开始执行
13
14-- 执行者:
15-- 开始时间:
16+- 执行者:`Codex`
17+- 开始时间:`2026-03-29 01:30:13 CST`
18 - 状态变更:`待开始` → `进行中`
19
20 ### 完成摘要
21
22-- 完成时间:
23+- 完成时间:`2026-03-29 01:34:40 CST`
24 - 状态变更:`进行中` → `已完成`
25 - 修改了哪些文件:
26+ - `apps/conductor-daemon/src/local-api.ts`
27+ - `apps/conductor-daemon/src/index.test.js`
28+ - `tasks/T-BUG-026.md`
29 - 核心实现思路:
30+ - 给 `handleConductorHttpRequest` 及 codexd/claude-coded 代理读取增加快照空值保护,避免缺失可选 backend snapshot 字段时直接抛出 `TypeError`
31+ - 补全 `index.test.js` 里的本地 API 测试夹具与持久化重启场景快照,显式提供 `claudeCoded.localApiBase: null`
32+ - 保持测试语义不变,只修正 context/snapshot 初始化缺失与运行时代码的默认值保护
33 - 跑了哪些测试:
34+ - `pnpm build`
35+ - `pnpm -F @baa-conductor/conductor-daemon test`
36+ - `pnpm test`
37
38 ### 执行过程中遇到的问题
39
40 > 记录执行过程中遇到的阻塞、环境问题、临时绕过方案等。合并时由合并者判断是否需要修复或建新任务。
41
42+- 新 worktree 初始没有安装依赖,首次运行 `pnpm -F @baa-conductor/conductor-daemon test` 因 `pnpm exec tsc` 找不到命令失败;在 worktree 内执行 `pnpm install` 后完成复现与修复验证。
43+
44 ### 剩余风险
45
46+- 当前修复覆盖了测试夹具缺失 `claudeCoded.localApiBase` 的场景,并为 local-api 增加了缺省保护;若后续引入新的最小快照夹具,仍建议保持与 `ConductorRuntimeApiSnapshot` 一致的结构。