Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save arikon/1f5af782bfb42e919b41bb2bc2c86d76 to your computer and use it in GitHub Desktop.

Select an option

Save arikon/1f5af782bfb42e919b41bb2bc2c86d76 to your computer and use it in GitHub Desktop.
oh-my-codex #3321: macOS /var path canonicalization patch
From 435e778f4a17bda4a0eee070536a4086261659f0 Mon Sep 17 00:00:00 2001
From: Sergey Belov <peimei@ya.ru>
Date: Tue, 28 Jul 2026 00:12:30 +0200
Subject: [PATCH] fix(hook): canonicalize macOS var paths
---
.../__tests__/codex-native-hook.test.ts | 7 +-
src/scripts/codex-native-hook.ts | 89 +++++++++++++------
src/utils/__tests__/paths.test.ts | 22 +++++
src/utils/paths.ts | 18 ++--
4 files changed, 101 insertions(+), 35 deletions(-)
diff --git a/src/scripts/__tests__/codex-native-hook.test.ts b/src/scripts/__tests__/codex-native-hook.test.ts
index 05f96ed2..0cc3debd 100644
--- a/src/scripts/__tests__/codex-native-hook.test.ts
+++ b/src/scripts/__tests__/codex-native-hook.test.ts
@@ -64,6 +64,7 @@ import {
import { getBaseStateDir } from "../../state/paths.js";
import { maybeNudgeLeaderForAllowedWorkerStop } from "../notify-hook/team-worker-stop.js";
import { MAX_NATIVE_STDIN_JSON_BYTES } from "../hook-payload-guard.js";
+import { sameFilePath } from "../../utils/paths.js";
const ARGUMENT_PRODUCING_RUNTIME_DENIAL_COMMANDS = [
@@ -5048,10 +5049,10 @@ PY`,
workingDirectory: cwd,
});
assert.notEqual(terminalWrite.isError, true);
- assert.equal(
- (terminalWrite.payload as { path?: string }).path,
+ assert.equal(sameFilePath(
+ String((terminalWrite.payload as { path?: string }).path ?? ""),
join(stateDir, "sessions", canonicalSessionId, "deep-interview-state.json"),
- );
+ ), true);
assert.equal(existsSync(join(stateDir, "sessions", ownerSessionId, "deep-interview-state.json")), false);
const stop = await dispatchCodexNativeHook(
diff --git a/src/scripts/codex-native-hook.ts b/src/scripts/codex-native-hook.ts
index 9a13f337..e81eeecc 100644
--- a/src/scripts/codex-native-hook.ts
+++ b/src/scripts/codex-native-hook.ts
@@ -74,7 +74,12 @@ import {
writeTeamPhase,
} from "../team/state.js";
import { parseTeamNoticeLedgerPrompt, reconcileTeamNoticeLedger } from "../team/notice-ledger.js";
-import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
+import {
+ canonicalizeComparablePath,
+ omxNotepadPath,
+ resolveProjectMemoryPath,
+ sameFilePath,
+} from "../utils/paths.js";
import { findGitLayout } from "../utils/git-layout.js";
import {
getAuthoritativeActiveStatePaths,
@@ -2804,14 +2809,14 @@ async function hasAuthoritativeTeamWorkerContext(cwd: string): Promise<boolean>
const config = await readJsonIfExists(join(teamRoot, "config.json"));
if (!identity || !manifest || !config) return false;
- const canonicalStateRoot = resolve(stateRoot);
- const canonicalCwd = resolve(cwd);
- const canonicalLeaderCwd = resolve(safeString(process.env.OMX_TEAM_LEADER_CWD).trim() || cwd);
+ const canonicalStateRoot = canonicalizeComparablePath(stateRoot);
+ const canonicalCwd = canonicalizeComparablePath(cwd);
+ const canonicalLeaderCwd = canonicalizeComparablePath(safeString(process.env.OMX_TEAM_LEADER_CWD).trim() || cwd);
const pathMatches = (value: unknown, expected: string): boolean => {
const candidate = safeString(value).trim();
if (!candidate) return false;
try {
- return resolve(candidate) === expected;
+ return sameFilePath(candidate, expected);
} catch {
return false;
}
@@ -3748,7 +3753,7 @@ function hookCancelObject(bytes: Buffer): Record<string, unknown> | null {
function hookCancelString(value: unknown): string { return typeof value === "string" ? value.trim() : ""; }
function hookCancelCwdMatches(value: Record<string, unknown>, canonicalCwd: string): boolean {
const recorded = hookCancelString(value.cwd) || hookCancelString(value.workingDirectory);
- try { return !recorded || resolve(recorded) === canonicalCwd; } catch { return false; }
+ try { return !recorded || sameFilePath(recorded, canonicalCwd); } catch { return false; }
}
function hookCancelSessionMatches(value: Record<string, unknown>, sessionId: string, cwd: string): boolean {
const recorded = hookCancelString(value.session_id);
@@ -4094,7 +4099,7 @@ function isFreshNativeSubagentCapacityBlocker(
const blockerCwd = safeString(blocker.cwd).trim();
if (blockerCwd) {
try {
- if (resolve(blockerCwd) !== resolve(cwd)) return false;
+ if (!sameFilePath(blockerCwd, cwd)) return false;
} catch {
return false;
}
@@ -4336,10 +4341,7 @@ function normalizePlanningArtifactRelativePath(cwd: string, rawPath: string): st
const trimmed = rawPath.trim();
if (!trimmed || trimmed.includes("\0")) return null;
try {
- const absolute = resolve(cwd, trimmed);
- const relativePath = relative(cwd, absolute).replace(/\\/g, "/");
- if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/")) return null;
- return relativePath;
+ return normalizePolicyRelativePath(cwd, resolve(cwd, trimmed));
} catch {
return null;
}
@@ -8884,7 +8886,7 @@ function isStandaloneParsedOmxStateWriteTransport(cwd: string, command: string,
|| safeString(payload.session_id).trim() !== authoritativeSessionId
|| !suppliedSessionAliasesMatch(payload, authoritativeSessionId)
|| safeString(payload.workingDirectory).trim() === ""
- || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false;
+ || !sameFilePath(safeString(payload.workingDirectory), cwd)) return false;
const usesInputFile = readStateWriteFlagValue(stateWriteOperation.args, "--input-file") !== undefined;
if (usesInputFile && splitStateScanSegments(canonicalCommand).length !== 1) return false;
if (extractDeepInterviewCommandRedirectTargets(command).length > 0) return false;
@@ -9033,7 +9035,7 @@ export function evaluateDeepInterviewRalplanHandoffCommand(
if (!suppliedSessionAliasesMatch(payload, authoritativeSessionId)) return rejectDeepInterviewRalplanHandoff("session_alias_conflict");
const workingDirectory = safeString(payload.workingDirectory).trim();
if (!workingDirectory) return rejectDeepInterviewRalplanHandoff("working_directory_missing");
- if (resolve(workingDirectory) !== resolve(cwd)) return rejectDeepInterviewRalplanHandoff("working_directory_mismatch");
+ if (!sameFilePath(workingDirectory, cwd)) return rejectDeepInterviewRalplanHandoff("working_directory_mismatch");
const targets = extractDeepInterviewCommandWriteTargets(command);
if (targets.length === 0) {
if (hasPriorExecutableCommand(stateWriteOperation.prefix)) return rejectDeepInterviewRalplanHandoff("unsafe_transport");
@@ -9086,7 +9088,7 @@ function isCompleteRalplanTerminalWritePayload(
).trim();
if (payloadSessionId !== sessionId) return false;
if (!suppliedSessionAliasesMatch(payload, sessionId)) return false;
- if (safeString(payload.workingDirectory).trim() === "" || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false;
+ if (safeString(payload.workingDirectory).trim() === "" || !sameFilePath(safeString(payload.workingDirectory), cwd)) return false;
if (activeSessionId && payloadSessionId !== activeSessionId) return false;
return true;
}
@@ -9154,7 +9156,7 @@ function isCompleteDeepInterviewTerminalWritePayload(
const phase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase();
const payloadSessionId = safeString(payload.session_id).trim();
if (!hasOnlyFinishedExplicitOutcomes(payload)) return false;
- if (safeString(payload.workingDirectory).trim() === "" || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false;
+ if (safeString(payload.workingDirectory).trim() === "" || !sameFilePath(safeString(payload.workingDirectory), cwd)) return false;
return mode === "deep-interview"
&& payload.active === false
&& phase === "complete"
@@ -9540,7 +9542,7 @@ function isAllowedDeepInterviewBashWrite(
|| safeString(payload.session_id).trim() !== sessionId
|| !suppliedSessionAliasesMatch(payload, sessionId)
|| safeString(payload.workingDirectory).trim() === ""
- || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false;
+ || !sameFilePath(safeString(payload.workingDirectory), cwd)) return false;
}
if (commandHasUntargetedPlanningForbiddenIntent(command)) return false;
if (firstPlanningTmpScriptExecutionTarget(cwd, command)) return false;
@@ -10505,12 +10507,45 @@ function normalizeRepoRelativePath(cwd: string, rawPath: string): string | null
const candidate = rawPath.trim();
if (!candidate || isUnresolvedVariableTarget(candidate)) return null;
const absolute = isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate);
- let relativePath = relative(cwd, absolute).replace(/\\/g, "/");
- if (!relativePath || relativePath === ".") return null;
- if (relativePath.startsWith("../") || relativePath === "..") {
- relativePath = candidate.replace(/\\/g, "/");
+ const canonicalRelative = normalizePolicyRelativePath(cwd, absolute);
+ if (canonicalRelative) return canonicalRelative;
+
+ let lexicalRelative = relative(cwd, absolute).replace(/\\/g, "/");
+ if (!lexicalRelative || lexicalRelative === ".") return null;
+ if (lexicalRelative.startsWith("../") || lexicalRelative === "..") {
+ lexicalRelative = candidate.replace(/\\/g, "/");
+ }
+ return lexicalRelative.replace(/^\.\//, "");
+}
+
+function normalizePolicyRelativePath(policyRoot: string, candidatePath: string): string | null {
+ const lexicalRoot = resolve(policyRoot);
+ const lexicalCandidate = resolve(candidatePath);
+ const lexicalRelative = relative(lexicalRoot, lexicalCandidate).replace(/\\/g, "/");
+ if (
+ lexicalRelative
+ && lexicalRelative !== "."
+ && !lexicalRelative.startsWith("../")
+ && lexicalRelative !== ".."
+ && conductorPathTraversesLink(lexicalRoot, lexicalRelative)
+ ) {
+ return null;
+ }
+
+ const canonicalRoot = canonicalizeComparablePath(lexicalRoot);
+ const canonicalCandidate = canonicalizeComparablePath(lexicalCandidate);
+ const canonicalRelative = relative(canonicalRoot, canonicalCandidate).replace(/\\/g, "/");
+ if (
+ !canonicalRelative
+ || canonicalRelative === "."
+ || canonicalRelative.startsWith("../")
+ || canonicalRelative === ".."
+ || canonicalRelative.startsWith("/")
+ ) {
+ return null;
}
- return relativePath.replace(/^\.\//, "");
+ if (conductorPathTraversesLink(canonicalRoot, canonicalRelative)) return null;
+ return canonicalRelative.replace(/^\.\//, "");
}
function conductorPathTraversesLink(cwd: string, relativePath: string): boolean {
@@ -17758,7 +17793,7 @@ function scanConductorShellSegment(
}
const omxGjcPathIsSafeForStaticOrchestration = trustedOmxGjcPackageCliPath;
const omxGjcStateWriteDestinationIsCanonical = activeState.effectiveCwd !== null
- && resolve(activeState.effectiveCwd) === resolve(rootCwd)
+ && sameFilePath(activeState.effectiveCwd, rootCwd)
&& !conductorInvocationUsesEnvCwdChangingWrapper(words, commandStartIndex, commandIndex);
const mainRootStructuredStateWrite = omxGjcInheritedRootsAreCanonical
&& omxGjcPrefixEnvironmentIsSafe
@@ -18865,7 +18900,7 @@ function conductorStateWriteTransportIsBoundToActiveSession(
|| safeString(statePayload.session_id).trim() !== authoritativeSessionId
|| !suppliedSessionAliasesMatch(statePayload, authoritativeSessionId)
|| safeString(statePayload.workingDirectory).trim() === ""
- || resolve(safeString(statePayload.workingDirectory)) !== resolve(cwd)
+ || !sameFilePath(safeString(statePayload.workingDirectory), cwd)
) return false;
// Inherited hook environment is process-authenticated; model-controlled shell
// assignments must match the session resolved for this PreToolUse payload.
@@ -19158,12 +19193,12 @@ async function directConductorStateWritePayloadHasExactSchema(
while (nestedState) {
if (!suppliedSessionAliasesMatch(nestedState, canonicalSessionId)) return false;
const nestedWorkingDirectory = safeString(nestedState.workingDirectory).trim();
- if (nestedWorkingDirectory && resolve(nestedWorkingDirectory) !== resolve(policyCwd)) return false;
+ if (nestedWorkingDirectory && !sameFilePath(nestedWorkingDirectory, policyCwd)) return false;
const nestedMode = safeString(nestedState.mode).trim();
if (nestedMode && nestedMode !== safeString(input.mode).trim()) return false;
nestedState = nestedState.state === undefined ? null : safeObject(nestedState.state);
}
- if (safeString(input.workingDirectory).trim() === "" || resolve(safeString(input.workingDirectory)) !== resolve(policyCwd)) return false;
+ if (safeString(input.workingDirectory).trim() === "" || !sameFilePath(safeString(input.workingDirectory), policyCwd)) return false;
return true;
}
@@ -19462,7 +19497,7 @@ function modeStateMatchesSkillStopContext(
).trim();
if (stateCwd) {
try {
- if (resolve(stateCwd) !== resolve(cwd)) return false;
+ if (!sameFilePath(stateCwd, cwd)) return false;
} catch {
return false;
}
@@ -19481,7 +19516,7 @@ function modeStateHasExplicitMatchingCwd(state: Record<string, unknown>, cwd: st
if (!stateCwd) return false;
try {
- return resolve(stateCwd) === resolve(cwd);
+ return sameFilePath(stateCwd, cwd);
} catch {
return false;
}
diff --git a/src/utils/__tests__/paths.test.ts b/src/utils/__tests__/paths.test.ts
index dd8025ae..14af1f4f 100644
--- a/src/utils/__tests__/paths.test.ts
+++ b/src/utils/__tests__/paths.test.ts
@@ -25,6 +25,7 @@ import {
omxLogsDir,
packageRoot,
canonicalizeComparablePath,
+ sameFilePath,
OMX_ENTRY_PATH_ENV,
OMX_STARTUP_CWD_ENV,
rememberOmxLaunchContext,
@@ -32,6 +33,27 @@ import {
resolveOmxEntryPath,
} from "../paths.js";
+describe("comparable paths", () => {
+ it("leaves ordinary paths under a canonical root unchanged", async () => {
+ const root = await realpath(await mkdtemp(join(tmpdir(), "omx-comparable-path-")));
+ try {
+ const missingChild = join(root, "missing", "child.json");
+ assert.equal(canonicalizeComparablePath(missingChild), missingChild);
+ assert.equal(sameFilePath(missingChild, missingChild), true);
+ } finally {
+ await rm(root, { recursive: true, force: true });
+ }
+ });
+
+ it("treats the macOS /var alias as the same path for missing descendants", {
+ skip: process.platform !== "darwin",
+ }, () => {
+ const aliasPath = join("/var", "tmp", "omx-comparable-missing", "state.json");
+ const canonicalPath = join("/private", "var", "tmp", "omx-comparable-missing", "state.json");
+ assert.equal(sameFilePath(aliasPath, canonicalPath), true);
+ });
+});
+
describe("codexHome", () => {
let originalCodexHome: string | undefined;
let originalUserProfile: string | undefined;
diff --git a/src/utils/paths.ts b/src/utils/paths.ts
index a4a2b781..71713676 100644
--- a/src/utils/paths.ts
+++ b/src/utils/paths.ts
@@ -6,7 +6,7 @@
import { createHash } from "crypto";
import { existsSync, realpathSync } from "fs";
import { readdir, readFile, realpath } from "fs/promises";
-import { dirname, isAbsolute, join, resolve } from "path";
+import { basename, dirname, isAbsolute, join, resolve } from "path";
import { homedir } from "os";
import { fileURLToPath } from "url";
@@ -49,11 +49,19 @@ function resolveLauncherPath(rawPath: string, baseCwd: string): string {
export function canonicalizeComparablePath(rawPath: string): string {
const absolutePath = resolve(rawPath);
- if (!existsSync(absolutePath)) return absolutePath;
+ let existingPath = absolutePath;
+ const missingSegments: string[] = [];
+ while (!existsSync(existingPath)) {
+ const parent = dirname(existingPath);
+ if (parent === existingPath) return absolutePath;
+ missingSegments.unshift(basename(existingPath));
+ existingPath = parent;
+ }
try {
- return typeof realpathSync.native === "function"
- ? realpathSync.native(absolutePath)
- : realpathSync(absolutePath);
+ const canonicalExistingPath = typeof realpathSync.native === "function"
+ ? realpathSync.native(existingPath)
+ : realpathSync(existingPath);
+ return resolve(canonicalExistingPath, ...missingSegments);
} catch {
return absolutePath;
}
--
2.50.1 (Apple Git-155)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment