fix: fail closed on invalid codex resume bindings
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import os from "node:os";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { constants } from "node:fs";
|
||||
import { access, stat } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
@@ -21,6 +23,20 @@ function resolveResumeTarget(config, task) {
|
||||
};
|
||||
}
|
||||
|
||||
function defaultCodexPath(relativePath) {
|
||||
return resolve(os.homedir(), ".codex", relativePath);
|
||||
}
|
||||
|
||||
function loadThreadWorkspaceHints(globalStatePath) {
|
||||
try {
|
||||
const raw = readFileSync(resolve(globalStatePath), "utf8");
|
||||
const parsed = JSON.parse(raw);
|
||||
return new Map(Object.entries(parsed["thread-workspace-root-hints"] ?? {}));
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
function shouldPreflightResumeTask(task) {
|
||||
const taskType = String(task?.taskType || "").trim();
|
||||
if (taskType === "dispatch_execution") {
|
||||
@@ -47,7 +63,7 @@ function buildStructuredTaskBindingError(code, message, details) {
|
||||
}
|
||||
|
||||
function inspectCodexThreadBinding(config, targetThreadRef, targetFolderRef) {
|
||||
const stateDbPath = trimToDefined(config?.codexStateDbPath);
|
||||
const stateDbPath = trimToDefined(config?.codexStateDbPath || defaultCodexPath("state_5.sqlite"));
|
||||
if (!stateDbPath) {
|
||||
return {
|
||||
status: "skipped",
|
||||
@@ -66,7 +82,10 @@ function inspectCodexThreadBinding(config, targetThreadRef, targetFolderRef) {
|
||||
};
|
||||
}
|
||||
|
||||
const threadCwd = trimToDefined(row.cwd) || "";
|
||||
const workspaceHints = loadThreadWorkspaceHints(
|
||||
trimToDefined(config?.codexGlobalStatePath || defaultCodexPath(".codex-global-state.json")),
|
||||
);
|
||||
const threadCwd = trimToDefined(workspaceHints.get(targetThreadRef)) || trimToDefined(row.cwd) || "";
|
||||
if (threadCwd && resolve(threadCwd) !== resolve(targetFolderRef)) {
|
||||
return {
|
||||
status: "mismatch",
|
||||
@@ -129,22 +148,6 @@ export async function prepareCodexTaskExecution(config, task, outputFile) {
|
||||
};
|
||||
}
|
||||
|
||||
if (bindingInspection.status === "mismatch") {
|
||||
return {
|
||||
ok: false,
|
||||
error: buildStructuredTaskBindingError(
|
||||
"LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH",
|
||||
`LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH: 目标线程绑定的 cwd 与当前目录不一致,已拒绝 codex exec resume。cwd=${resumeTarget.cwd} liveCwd=${bindingInspection.threadCwd}`,
|
||||
{
|
||||
cwd: resumeTarget.cwd,
|
||||
liveCwd: bindingInspection.threadCwd,
|
||||
targetThreadRef,
|
||||
targetCodexFolderRef: resumeTarget.targetFolderRef,
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const folderStat = await stat(resumeTarget.cwd);
|
||||
if (!folderStat.isDirectory()) {
|
||||
@@ -166,6 +169,22 @@ export async function prepareCodexTaskExecution(config, task, outputFile) {
|
||||
};
|
||||
}
|
||||
|
||||
if (bindingInspection.status === "mismatch") {
|
||||
return {
|
||||
ok: false,
|
||||
error: buildStructuredTaskBindingError(
|
||||
"LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH",
|
||||
`LOCAL_AGENT_CODEX_THREAD_BINDING_MISMATCH: 目标线程绑定的 cwd 与当前目录不一致,已拒绝 codex exec resume。cwd=${resumeTarget.cwd} liveCwd=${bindingInspection.threadCwd}`,
|
||||
{
|
||||
cwd: resumeTarget.cwd,
|
||||
liveCwd: bindingInspection.threadCwd,
|
||||
targetThreadRef,
|
||||
targetCodexFolderRef: resumeTarget.targetFolderRef,
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
execution: buildCodexTaskExecution(config, task, outputFile),
|
||||
|
||||
Reference in New Issue
Block a user