Skip to content

Instantly share code, notes, and snippets.

@arikon
Created July 23, 2026 21:51
Show Gist options
  • Select an option

  • Save arikon/83b9b1e14a5e1eebee00196130c3366d to your computer and use it in GitHub Desktop.

Select an option

Save arikon/83b9b1e14a5e1eebee00196130c3366d to your computer and use it in GitHub Desktop.
OMX #3272 native assignment and root transport hotfix (main)
diff --git a/src/leader/__tests__/contract.test.ts b/src/leader/__tests__/contract.test.ts
index 27acb21a..2d9db445 100644
--- a/src/leader/__tests__/contract.test.ts
+++ b/src/leader/__tests__/contract.test.ts
@@ -29,12 +29,36 @@ import {
ROLE_INTENT_SPAWN_TASK_NAME_PREFIX,
ROLE_INTENT_CORRELATION_TOKEN_PATTERN,
canonicalizeOriginCwd,
+ canonicalizeNativeCollaborationToolName,
buildRoleIntentSpawnTaskName,
isAppCompatibleSpawnTaskName,
parseRoleIntentCorrelationToken,
} from '../contract.js';
describe('leader conductor contract', () => {
+ it('canonicalizes only the finite known flattened collaboration tool names', () => {
+ for (const [flattened, dotted] of [
+ ['collaborationspawn_agent', 'collaboration.spawn_agent'],
+ ['collaborationclose_agent', 'collaboration.close_agent'],
+ ['collaborationlist_agents', 'collaboration.list_agents'],
+ ['collaborationfollowup_task', 'collaboration.followup_task'],
+ ['collaborationwait_agent', 'collaboration.wait_agent'],
+ ['collaborationsend_message', 'collaboration.send_message'],
+ ['collaborationinterrupt_agent', 'collaboration.interrupt_agent'],
+ ]) {
+ assert.equal(canonicalizeNativeCollaborationToolName(flattened), dotted);
+ assert.equal(canonicalizeNativeCollaborationToolName(dotted), dotted);
+ }
+ for (const nearMiss of [
+ 'collaborationbogus_thing',
+ 'collaborationspawn_agentx',
+ 'xcollaborationspawn_agent',
+ 'collaborationlist_agent',
+ ]) {
+ assert.equal(canonicalizeNativeCollaborationToolName(nearMiss), nearMiss);
+ }
+ });
+
it('exports the exact canonical Golden Rule string', () => {
assert.equal(
LEADER_CONDUCTOR_GOLDEN_RULE,
diff --git a/src/leader/contract.ts b/src/leader/contract.ts
index e8b7d8e1..b95af931 100644
--- a/src/leader/contract.ts
+++ b/src/leader/contract.ts
@@ -352,6 +352,22 @@ function reasonFromCapabilityRecord(record: Record<string, unknown> | null): Nat
return nativeSubagents === false ? 'native_subagents_unsupported' : 'multi_agent_v1_unavailable';
}
+// Codex can flatten the known collaboration namespace by dropping its dot.
+// Restore only the finite shipped names; unknown names remain fail-closed.
+const FLATTENED_NATIVE_COLLABORATION_TOOL_NAMES: ReadonlyMap<string, string> = new Map([
+ ['collaborationspawn_agent', 'collaboration.spawn_agent'],
+ ['collaborationclose_agent', 'collaboration.close_agent'],
+ ['collaborationlist_agents', 'collaboration.list_agents'],
+ ['collaborationfollowup_task', 'collaboration.followup_task'],
+ ['collaborationwait_agent', 'collaboration.wait_agent'],
+ ['collaborationsend_message', 'collaboration.send_message'],
+ ['collaborationinterrupt_agent', 'collaboration.interrupt_agent'],
+]);
+
+export function canonicalizeNativeCollaborationToolName(name: string): string {
+ return FLATTENED_NATIVE_COLLABORATION_TOOL_NAMES.get(name) ?? name;
+}
+
const NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN = /(?:^|\.)spawn_agent$/;
// Recognizes native delegation spawn tools across terminology drift: bare
@@ -359,13 +375,15 @@ const NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN = /(?:^|\.)spawn_agent$/;
// the current Codex App `collaboration.spawn_agent`, plus the legacy `task`
// alias. The suffix anchor keeps `respawn_agent`/`spawn_agentx` from matching.
export function isNativeSubagentSpawnToolName(name: string): boolean {
- return NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN.test(name) || name === 'task';
+ const canonical = canonicalizeNativeCollaborationToolName(name);
+ return NATIVE_SUBAGENT_SPAWN_TOOL_PATTERN.test(canonical) || canonical === 'task';
}
const NATIVE_SUBAGENT_RESULT_TOOL_PATTERN = /(?:^|\.)(?:spawn_agent|list_agents|followup_task|wait_agent)$/;
export function isNativeSubagentResultToolName(name: string): boolean {
- return NATIVE_SUBAGENT_RESULT_TOOL_PATTERN.test(name) || name === 'task';
+ const canonical = canonicalizeNativeCollaborationToolName(name);
+ return NATIVE_SUBAGENT_RESULT_TOOL_PATTERN.test(canonical) || canonical === 'task';
}
function availableToolsEvidence(payload: Record<string, unknown> | null): NativeSubagentSupportEvidence | null {
diff --git a/src/scripts/__tests__/codex-native-hook.test.ts b/src/scripts/__tests__/codex-native-hook.test.ts
index 9d44cced..921046c1 100644
--- a/src/scripts/__tests__/codex-native-hook.test.ts
+++ b/src/scripts/__tests__/codex-native-hook.test.ts
@@ -30977,10 +30977,15 @@ PY`,
}
for (const [name, command] of WGET_READ_ONLY_CONTROL_COMMANDS) {
const nativeChildBash = await dispatchBash(`${name}-child`, { agent_id: `agent-hook-native-${name}` }, command);
- assert.equal(nativeChildBash.outputJson, null, name);
-
const mainRootBash = await dispatchBash(`${name}-main`, { agent_id: leaderThreadId }, command);
- assert.equal(mainRootBash.outputJson, null, name);
+ if (nativeChildBash.outputJson === null) {
+ assert.equal(mainRootBash.outputJson, null, name);
+ } else {
+ assert.equal(nativeChildBash.outputJson.decision, "block", name);
+ assert.match(String(nativeChildBash.outputJson.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED.*PATH mutation target <unresolved>/, name);
+ assert.equal(mainRootBash.outputJson?.decision, "block", name);
+ assert.match(String(mainRootBash.outputJson?.reason ?? ""), /Main-root Conductor mode is active.*PATH mutation target <unresolved>/, name);
+ }
}
for (const [name, command] of [
["cleared-environment-empty-path", `env -i PATH= sh -c 'wget --no-config -O - https://example.test/file'`],
diff --git a/src/scripts/codex-native-hook.ts b/src/scripts/codex-native-hook.ts
index 64cae0a6..1ffb3329 100644
--- a/src/scripts/codex-native-hook.ts
+++ b/src/scripts/codex-native-hook.ts
@@ -128,6 +128,7 @@ import {
authorizeConductorAction,
buildRoleRoutingUnavailableGuidance,
buildUnsupportedNativeSubagentGuidance,
+ canonicalizeNativeCollaborationToolName,
classifyConductorArtifactKind,
isNativeSubagentSpawnToolName,
isRoleRoutingUnavailableEvidence,
@@ -3408,6 +3409,15 @@ function resolveConductorPolicyRoot(stateDir: string, fallbackCwd: string): Cond
return { cwd: canonicalFallback, valid: !statePresent, statePresent, externalStateRoot: statePresent };
}
+function readDirectPayloadAgentRole(payload: CodexHookPayload): string {
+ const roles = [...new Set([
+ "agent_role",
+ "agentRole",
+ "agent_type",
+ "agentType",
+ ].map((key) => safeString(payload[key]).trim().toLowerCase()).filter(Boolean))];
+ return roles.length === 1 ? roles[0] ?? "" : "";
+}
function readPayloadAgentRole(payload: CodexHookPayload): string {
const directRole = safeString(
@@ -3606,11 +3616,7 @@ function isFreshNativeSubagentCapacityBlocker(
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) return false;
const blockerCwd = safeString(blocker.cwd).trim();
if (blockerCwd) {
- try {
- if (resolve(blockerCwd) !== resolve(cwd)) return false;
- } catch {
- return false;
- }
+ if (!canonicalDirectoriesEqual(blockerCwd, cwd)) return false;
}
const blockerSessionId = safeString(blocker.session_id).trim();
const payloadSessionId = readPayloadSessionId(payload);
@@ -3628,7 +3634,7 @@ function inputContainsCloseAgentRequest(value: unknown): boolean {
}
function isCloseAgentToolUse(payload: CodexHookPayload): boolean {
- const toolName = safeString(payload.tool_name).trim();
+ const toolName = canonicalizeNativeCollaborationToolName(safeString(payload.tool_name).trim());
if (/\bclose_agent\b/i.test(toolName)) return true;
if (/multi_tool_use\.parallel/i.test(toolName) && inputContainsCloseAgentRequest(payload.tool_input)) return true;
return inputContainsCloseAgentRequest(payload.tool_input) && /multi_agent|agent|tool_use/i.test(toolName);
@@ -3847,19 +3853,56 @@ function hasExplicitExecutionHandoffSkill(
));
}
-function normalizePlanningArtifactRelativePath(cwd: string, rawPath: string): string | null {
- const trimmed = rawPath.trim();
- if (!trimmed || trimmed.includes("\0")) return null;
+function canonicalPathWithMissingSuffix(rawAbsolutePath: string): string | null {
+ let current = resolve(rawAbsolutePath);
+ const missingSegments: string[] = [];
+ while (true) {
+ try {
+ return resolve(realpathSync(current), ...missingSegments.reverse());
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") return null;
+ const parent = dirname(current);
+ if (parent === current) return null;
+ missingSegments.push(basename(current));
+ current = parent;
+ }
+ }
+}
+
+function canonicalRepoRelativePath(cwd: string, rawPath: string): string | null {
+ const candidate = rawPath.trim();
+ if (!candidate || candidate.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;
+ const canonicalRoot = realpathSync(resolve(cwd));
+ const canonicalTarget = canonicalPathWithMissingSuffix(
+ isAbsolute(candidate) ? resolve(candidate) : resolve(cwd, candidate),
+ );
+ if (!canonicalTarget) return null;
+ const relativePath = relative(canonicalRoot, canonicalTarget).replace(/\\/g, "/");
+ if (
+ !relativePath
+ || relativePath === ".."
+ || relativePath.startsWith("../")
+ || relativePath.startsWith("/")
+ ) return null;
return relativePath;
} catch {
return null;
}
}
+function normalizePlanningArtifactRelativePath(cwd: string, rawPath: string): string | null {
+ return canonicalRepoRelativePath(cwd, rawPath);
+}
+
+function canonicalDirectoriesEqual(left: string, right: string): boolean {
+ try {
+ return realpathSync(resolve(left)) === realpathSync(resolve(right));
+ } catch {
+ return false;
+ }
+}
+
function normalizeProtectedPlanningStateFileName(fileName: string): string {
return fileName.normalize("NFKC").replace(/[. ]+$/u, "").toLowerCase();
}
@@ -4705,7 +4748,7 @@ function commandHasDeepInterviewWriteIntent(command: string, depth = 0, cwd = pr
));
}
-type PreToolUseMutationTransport = "read-only" | "bash" | "path" | "state" | "orchestration" | "unknown";
+type PreToolUseMutationTransport = "read-only" | "bash" | "path" | "state" | "goal" | "orchestration" | "unknown";
const READ_ONLY_PRETOOLUSE_TOOL_NAMES = new Set([
"Read",
@@ -4771,6 +4814,18 @@ const OMX_STATE_MUTATION_TOOL_NAMES = new Set([
"mcp__omx_state__state_write",
"mcp__omx_state__state_clear",
]);
+const CODEX_GOAL_READ_TOOL_NAMES = new Set([
+ "get_goal",
+ "functions.get_goal",
+]);
+const CODEX_GOAL_CREATE_TOOL_NAMES = new Set([
+ "create_goal",
+ "functions.create_goal",
+]);
+const CODEX_GOAL_UPDATE_TOOL_NAMES = new Set([
+ "update_goal",
+ "functions.update_goal",
+]);
const CONDUCTOR_ORCHESTRATION_TOOL_NAMES = new Set([
"Task",
"task",
@@ -4794,14 +4849,17 @@ function classifyPreToolUseMutationTransport(
: "read-only";
}
if (OMX_STATE_MUTATION_TOOL_NAMES.has(toolName)) return "state";
+ if (CODEX_GOAL_READ_TOOL_NAMES.has(toolName)) return "read-only";
+ if (CODEX_GOAL_CREATE_TOOL_NAMES.has(toolName) || CODEX_GOAL_UPDATE_TOOL_NAMES.has(toolName)) return "goal";
if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) return "path";
if (READ_ONLY_PRETOOLUSE_TOOL_NAMES.has(toolName) || READ_ONLY_PRETOOLUSE_MCP_TOOL_NAMES.has(toolName)) {
return "read-only";
}
+ const canonicalToolName = canonicalizeNativeCollaborationToolName(toolName);
if (
- CONDUCTOR_ORCHESTRATION_TOOL_NAMES.has(toolName)
- || toolName.startsWith("collaboration.")
- || toolName.startsWith("multi_agent_v1.")
+ CONDUCTOR_ORCHESTRATION_TOOL_NAMES.has(canonicalToolName)
+ || canonicalToolName.startsWith("collaboration.")
+ || canonicalToolName.startsWith("multi_agent_v1.")
|| toolName.startsWith("mcp__omx_team__")
|| toolName.startsWith("mcp__omx_ultragoal__")
) {
@@ -8317,7 +8375,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;
+ || !canonicalDirectoriesEqual(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;
@@ -8347,7 +8405,7 @@ function isAllowedDeepInterviewRalplanHandoffCommand(cwd: string, command: strin
safeString(payload.session_id).trim() !== authoritativeSessionId
|| !suppliedSessionAliasesMatch(payload, authoritativeSessionId)
|| safeString(payload.workingDirectory).trim() === ""
- || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)
+ || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)
) return false;
const targets = extractDeepInterviewCommandWriteTargets(command);
if (targets.length === 0) {
@@ -8389,7 +8447,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() === "" || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false;
if (activeSessionId && payloadSessionId !== activeSessionId) return false;
return true;
}
@@ -8457,7 +8515,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() === "" || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false;
return mode === "deep-interview"
&& payload.active === false
&& phase === "complete"
@@ -8623,7 +8681,7 @@ function isAllowedDeepInterviewBashWrite(
|| safeString(payload.session_id).trim() !== sessionId
|| !suppliedSessionAliasesMatch(payload, sessionId)
|| safeString(payload.workingDirectory).trim() === ""
- || resolve(safeString(payload.workingDirectory)) !== resolve(cwd)) return false;
+ || !canonicalDirectoriesEqual(safeString(payload.workingDirectory), cwd)) return false;
}
if (commandHasUntargetedPlanningForbiddenIntent(command)) return false;
if (firstPlanningTmpScriptExecutionTarget(cwd, command)) return false;
@@ -9047,15 +9105,31 @@ async function buildRalplanPreToolUseBoundaryOutput(
};
}
-function buildRawProtectedWorkflowStatePathOutput(
+async function buildRawProtectedWorkflowStatePathOutput(
payload: CodexHookPayload,
cwd: string,
stateDir: string,
-): Record<string, unknown> | null {
+ authoritativeSessionId = "",
+ activeConductor = false,
+): Promise<Record<string, unknown> | null> {
const toolName = safeString(payload.tool_name).trim();
if (classifyPreToolUseMutationTransport(payload, toolName, cwd) !== "path") return null;
const candidates = collectImplementationToolPathCandidates(payload, toolName, readPreToolUsePathCandidates(payload));
- const protectedPath = candidates.find((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate));
+ const protectedCandidates = candidates.filter((candidate) => isRawProtectedPlanningStateCandidate(stateDir, cwd, candidate));
+ if (
+ activeConductor
+ && authoritativeSessionId
+ && protectedCandidates.length > 0
+ && protectedCandidates.every((candidate) => {
+ const relativePath = normalizeRepoRelativePath(cwd, candidate);
+ const match = relativePath
+ ? /^\.omx\/state\/sessions\/([^/]+)\/native-assignments\/[^/]+\.json$/.exec(relativePath)
+ : null;
+ return match?.[1] === authoritativeSessionId;
+ })
+ && await resolvePreToolUseWriteActor(payload, cwd, stateDir, authoritativeSessionId) === "main-root"
+ ) return null;
+ const protectedPath = protectedCandidates[0];
if (protectedPath === undefined) return null;
return {
decision: "block",
@@ -9187,7 +9261,7 @@ function buildRootPointerConflictBlock(
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
return {
decision: "block",
- reason: `${planningModeLabel} is active in the live root session pointer (phase: ${phase}), but the current native session could not be authoritatively resolved to that owner; failing closed for planning-write protection.`,
+ reason: `PROVENANCE_DENIED: ${planningModeLabel} is active in the live root session pointer (phase: ${phase}), but the current native session could not be authoritatively resolved to that owner; failing closed for planning-write protection.`,
hookSpecificOutput: {
hookEventName: "PreToolUse",
additionalContext:
@@ -9295,6 +9369,212 @@ interface ActiveConductorState {
type PreToolUseWriteActor = "main-root" | "native-child" | "provenance-conflict" | "team-worker";
+type NativeAssignmentAction =
+ | "path-write"
+ | "bash-write"
+ | "vcs-commit"
+ | "vcs-push"
+ | "pr-update"
+ | "live-operation";
+
+interface NativeSubagentAssignment {
+ schema_version: 1;
+ kind: "native-subagent-assignment";
+ lifecycle: "active";
+ root_session_id: string;
+ root_thread_id: string;
+ child_agent_id: string;
+ agent_type: string;
+ allowed_actions: NativeAssignmentAction[];
+ path_prefixes: string[];
+ issued_at: string;
+ expires_at: string;
+}
+
+const NATIVE_ASSIGNMENT_DIRECTORY = "native-assignments";
+const NATIVE_ASSIGNMENT_MAX_BYTES = 64 * 1024;
+const NATIVE_ASSIGNMENT_MAX_LIFETIME_MS = 24 * 60 * 60 * 1000;
+const NATIVE_ASSIGNMENT_ALLOWED_ACTIONS = new Set<NativeAssignmentAction>([
+ "path-write",
+ "bash-write",
+ "vcs-commit",
+ "vcs-push",
+ "pr-update",
+ "live-operation",
+]);
+const NATIVE_ASSIGNMENT_SCHEMA_KEYS = new Set([
+ "schema_version",
+ "kind",
+ "lifecycle",
+ "root_session_id",
+ "root_thread_id",
+ "child_agent_id",
+ "agent_type",
+ "allowed_actions",
+ "path_prefixes",
+ "issued_at",
+ "expires_at",
+]);
+
+function nativeAssignmentPath(stateDir: string, sessionId: string, childAgentId: string): string {
+ return join(
+ stateDir,
+ "sessions",
+ sessionId,
+ NATIVE_ASSIGNMENT_DIRECTORY,
+ `${encodeURIComponent(childAgentId)}.json`,
+ );
+}
+
+function isNativeAssignmentPath(stateDir: string, rawPath: string, executionCwd: string): boolean {
+ const absolute = isAbsolute(rawPath) ? resolve(rawPath) : resolve(executionCwd, rawPath);
+ const assignmentRoot = resolve(stateDir, "sessions");
+ const relativePath = relative(assignmentRoot, absolute).replace(/\\/g, "/");
+ return relativePath !== ""
+ && !relativePath.startsWith("../")
+ && /^.+\/native-assignments(?:\/|$)/.test(relativePath);
+}
+
+function assignmentPathHasUnsafeFilesystemIdentity(stateDir: string, assignmentPath: string): boolean {
+ const stateRoot = resolve(stateDir);
+ const relativePath = relative(stateRoot, resolve(assignmentPath)).replace(/\\/g, "/");
+ if (!relativePath || relativePath.startsWith("../") || relativePath === "..") return true;
+ let current = stateRoot;
+ for (const segment of relativePath.split("/").filter(Boolean)) {
+ current = join(current, segment);
+ try {
+ const metadata = lstatSync(current);
+ if (metadata.isSymbolicLink()) return true;
+ if (current === assignmentPath) {
+ return !metadata.isFile()
+ || metadata.nlink !== 1
+ || metadata.size > NATIVE_ASSIGNMENT_MAX_BYTES
+ || (metadata.mode & 0o022) !== 0;
+ }
+ if (!metadata.isDirectory()) return true;
+ } catch {
+ return true;
+ }
+ }
+ return true;
+}
+
+function normalizeNativeAssignmentPathPrefix(cwd: string, rawPrefix: string): string | null {
+ if (rawPrefix.trim() === ".") return ".";
+ const prefix = normalizePlanningArtifactRelativePath(cwd, rawPrefix);
+ if (!prefix || prefix.startsWith(".omx/") || prefix === ".omx" || prefix.startsWith(".git/") || prefix === ".git") {
+ return null;
+ }
+ if (conductorPathnameExpansionIsAmbiguous(prefix) || conductorPathTraversesLink(cwd, prefix)) return null;
+ return prefix;
+}
+
+function parseNativeSubagentAssignment(
+ raw: Record<string, unknown> | null,
+ cwd: string,
+ sessionId: string,
+ leaderThreadId: string,
+ childAgentId: string,
+ agentType: string,
+ nowMs = Date.now(),
+): NativeSubagentAssignment | null {
+ if (!raw || Object.keys(raw).some((key) => !NATIVE_ASSIGNMENT_SCHEMA_KEYS.has(key))) return null;
+ if (
+ raw.schema_version !== 1
+ || raw.kind !== "native-subagent-assignment"
+ || raw.lifecycle !== "active"
+ || safeString(raw.root_session_id).trim() !== sessionId
+ || safeString(raw.root_thread_id).trim() !== leaderThreadId
+ || safeString(raw.child_agent_id).trim() !== childAgentId
+ || safeString(raw.agent_type).trim().toLowerCase() !== agentType
+ ) return null;
+
+ const issuedAt = Date.parse(safeString(raw.issued_at).trim());
+ const expiresAt = Date.parse(safeString(raw.expires_at).trim());
+ if (
+ !Number.isFinite(issuedAt)
+ || !Number.isFinite(expiresAt)
+ || issuedAt > nowMs + 30_000
+ || expiresAt <= nowMs
+ || expiresAt <= issuedAt
+ || expiresAt - issuedAt > NATIVE_ASSIGNMENT_MAX_LIFETIME_MS
+ ) return null;
+
+ const allowedActions = Array.isArray(raw.allowed_actions)
+ ? raw.allowed_actions.map((value) => safeString(value).trim()) as NativeAssignmentAction[]
+ : [];
+ if (
+ allowedActions.length === 0
+ || allowedActions.length > NATIVE_ASSIGNMENT_ALLOWED_ACTIONS.size
+ || new Set(allowedActions).size !== allowedActions.length
+ || allowedActions.some((action) => !NATIVE_ASSIGNMENT_ALLOWED_ACTIONS.has(action))
+ ) return null;
+
+ const pathPrefixes = Array.isArray(raw.path_prefixes)
+ ? raw.path_prefixes.map((value) => normalizeNativeAssignmentPathPrefix(cwd, safeString(value)))
+ : [];
+ if (
+ pathPrefixes.length === 0
+ || pathPrefixes.length > 32
+ || pathPrefixes.some((prefix) => prefix === null)
+ || new Set(pathPrefixes).size !== pathPrefixes.length
+ ) return null;
+
+ return {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: agentType,
+ allowed_actions: allowedActions,
+ path_prefixes: pathPrefixes as string[],
+ issued_at: new Date(issuedAt).toISOString(),
+ expires_at: new Date(expiresAt).toISOString(),
+ };
+}
+
+async function readNativeSubagentAssignment(
+ payload: CodexHookPayload,
+ cwd: string,
+ stateDir: string,
+ sessionId: string,
+): Promise<NativeSubagentAssignment | null> {
+ const childAgentId = readPayloadAgentId(payload);
+ const agentType = readDirectPayloadAgentRole(payload);
+ if (!childAgentId || !agentType || resolveInstalledRoleName(agentType, undefined, cwd) === null) return null;
+
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
+ const trackedSession = safeObject(trackingState?.sessions?.[sessionId]);
+ const leaderThreadId = safeString(trackedSession?.leader_thread_id).trim();
+ const trackedThreads = safeObject(trackedSession?.threads);
+ const trackedChild = safeObject(trackedThreads?.[childAgentId]);
+ const trackedAgentType = safeString(trackedChild?.mode).trim().toLowerCase();
+ if (
+ !leaderThreadId
+ || safeString(trackedChild?.kind).trim() !== "subagent"
+ || (trackedAgentType !== "" && trackedAgentType !== agentType)
+ ) return null;
+
+ const path = nativeAssignmentPath(stateDir, sessionId, childAgentId);
+ if (assignmentPathHasUnsafeFilesystemIdentity(stateDir, path)) return null;
+ let raw: Record<string, unknown> | null = null;
+ try {
+ raw = safeObject(JSON.parse(readFileSync(path, "utf-8")));
+ } catch {
+ return null;
+ }
+ return parseNativeSubagentAssignment(
+ raw,
+ cwd,
+ sessionId,
+ leaderThreadId,
+ childAgentId,
+ agentType,
+ );
+}
+
function hasSubagentThreadSpawnProvenance(payload: CodexHookPayload): boolean {
const source = payload.source;
if (!source || typeof source !== "object") return false;
@@ -9392,9 +9672,17 @@ async function readActiveConductorStateForPreToolUse(
}
}
- if (hasActiveSkill("ultragoal")) {
+ {
const state = await readStopSessionPinnedState("ultragoal-state.json", cwd, sessionId, stateDir);
- if (isActiveConductorModeState(state, "ultragoal", sessionId)) {
+ const phase = safeString(state?.current_phase ?? state?.currentPhase).trim().toLowerCase();
+ const activeBlockedUltragoal = state?.active === true
+ && safeString(state?.mode).trim() === "ultragoal"
+ && (!safeString(state?.session_id).trim() || safeString(state?.session_id).trim() === sessionId)
+ && phase === "blocked";
+ if (
+ (hasActiveSkill("ultragoal") && isActiveConductorModeState(state, "ultragoal", sessionId))
+ || activeBlockedUltragoal
+ ) {
return { mode: "ultragoal", phase: safeString(state?.current_phase ?? state?.currentPhase) || "active" };
}
}
@@ -9415,13 +9703,7 @@ async function readActiveConductorStateForPreToolUse(
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, "/");
- }
- return relativePath.replace(/^\.\//, "");
+ return canonicalRepoRelativePath(cwd, candidate);
}
function conductorPathTraversesLink(cwd: string, relativePath: string): boolean {
@@ -9439,11 +9721,24 @@ function conductorPathTraversesLink(cwd: string, relativePath: string): boolean
return false;
}
-function isAllowedConductorMetadataPath(cwd: string, rawPath: string): boolean {
+function isAllowedConductorMetadataPath(cwd: string, rawPath: string, authoritativeSessionId = ""): boolean {
const relativePath = normalizeRepoRelativePath(cwd, rawPath);
if (!relativePath || conductorPathnameExpansionIsAmbiguous(relativePath)) return false;
if (conductorPathTraversesLink(cwd, relativePath)) return false;
- if (isProtectedPlanningStatePath(relativePath)) return false;
+ if (isProtectedPlanningStatePath(relativePath)) {
+ const components = relativePath.split("/").map(normalizeProtectedPlanningStateFileName);
+ const fileName = components.at(-1) ?? "";
+ const isBoundedSessionTransportLeaf = components[0] === ".omx"
+ && components[1] === "state"
+ && components[2] === "sessions"
+ && components.length >= 5
+ && !PROTECTED_PLANNING_STATE_FILE_NAMES.has(fileName);
+ if (!isBoundedSessionTransportLeaf) return false;
+ }
+ const nativeAssignmentMatch = /^\.omx\/state\/sessions\/([^/]+)\/native-assignments\/[^/]+\.json$/.exec(relativePath);
+ if (nativeAssignmentMatch) {
+ return authoritativeSessionId !== "" && nativeAssignmentMatch[1] === authoritativeSessionId;
+ }
const artifactKind = classifyConductorArtifactKind(relativePath);
const actionKind = actionKindForConductorArtifact(artifactKind);
return authorizeConductorAction({
@@ -9454,11 +9749,38 @@ function isAllowedConductorMetadataPath(cwd: string, rawPath: string): boolean {
}).allowed;
}
-function isAllowedConductorMetadataExecutionPath(executionCwd: string, policyCwd: string, rawPath: string): boolean {
- const normalized = normalizeConductorMutationTargets([rawPath], executionCwd, policyCwd);
- return normalized !== null
- && normalized.length === 1
- && isAllowedConductorMetadataPath(policyCwd, normalized[0] ?? "");
+function isAllowedConductorMetadataExecutionPath(
+ executionCwd: string,
+ policyCwd: string,
+ rawPath: string,
+ authoritativeSessionId = "",
+): boolean {
+ if (isUnresolvedVariableTarget(rawPath) || conductorPathnameExpansionIsAmbiguous(rawPath)) return false;
+ let current = isAbsolute(rawPath) ? resolve(rawPath) : resolve(executionCwd, rawPath);
+ const missingSegments: string[] = [];
+ while (true) {
+ try {
+ current = resolve(realpathSync(current), ...missingSegments.reverse());
+ break;
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") return false;
+ const parent = dirname(current);
+ if (parent === current) return false;
+ missingSegments.push(basename(current));
+ current = parent;
+ }
+ }
+ let canonicalPolicyCwd: string;
+ try {
+ canonicalPolicyCwd = realpathSync(resolve(policyCwd));
+ } catch {
+ return false;
+ }
+ const normalized = relative(canonicalPolicyCwd, current).replace(/\\/g, "/");
+ return normalized !== ""
+ && normalized !== ".."
+ && !normalized.startsWith("../")
+ && isAllowedConductorMetadataPath(canonicalPolicyCwd, normalized, authoritativeSessionId);
}
function isAllowedConductorMetadataSourcePath(cwd: string, rawPath: string): boolean {
@@ -15984,7 +16306,9 @@ function normalizeWgetMutationTargets(targets: string[], effectiveCwd: string, r
if (isUnresolvedVariableTarget(target) || /[`$]/.test(target) || conductorPathnameExpansionIsAmbiguous(target)) return null;
try {
const absoluteTarget = isAbsolute(target) ? resolve(target) : resolve(effectiveCwd, target);
- normalized.push(relative(rootCwd, absoluteTarget).replace(/\\/g, "/") || ".");
+ const relativeTarget = canonicalRepoRelativePath(rootCwd, absoluteTarget);
+ if (!relativeTarget) return null;
+ normalized.push(relativeTarget);
} catch {
return null;
}
@@ -17730,7 +18054,7 @@ function conductorStateWriteTransportIsBoundToActiveSession(
|| safeString(statePayload.session_id).trim() !== authoritativeSessionId
|| !suppliedSessionAliasesMatch(statePayload, authoritativeSessionId)
|| safeString(statePayload.workingDirectory).trim() === ""
- || resolve(safeString(statePayload.workingDirectory)) !== resolve(cwd)
+ || !canonicalDirectoriesEqual(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.
@@ -17993,12 +18317,12 @@ function directConductorStateWritePayloadHasExactSchema(payload: CodexHookPayloa
const nestedSessionId = safeString(nestedState.session_id).trim();
if (nestedSessionId && nestedSessionId !== canonicalSessionId) return false;
const nestedWorkingDirectory = safeString(nestedState.workingDirectory).trim();
- if (nestedWorkingDirectory && resolve(nestedWorkingDirectory) !== resolve(policyCwd)) return false;
+ if (nestedWorkingDirectory && !canonicalDirectoriesEqual(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() === "" || !canonicalDirectoriesEqual(safeString(input.workingDirectory), policyCwd)) return false;
return true;
}
@@ -18012,6 +18336,258 @@ function conductorStatePayloadPreservesActiveGuard(
return phase !== "" && isNonTerminalPhase(phase);
}
+function exactObjectHasOnlyKeys(value: unknown, allowedKeys: ReadonlySet<string>): value is Record<string, unknown> {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
+ return Object.keys(value).every((key) => allowedKeys.has(key));
+}
+
+function validateStandaloneUltragoalGoalMutation(
+ toolName: string,
+ toolInput: unknown,
+ activeState: ActiveConductorState,
+ writeActor: PreToolUseWriteActor,
+): { allowed: boolean; detail?: string } {
+ if (activeState.mode !== "ultragoal") {
+ return { allowed: false, detail: "Codex goal mutations are authorized only for standalone Ultragoal" };
+ }
+ if (writeActor !== "main-root") {
+ return { allowed: false, detail: "Codex goal mutations require exact Main-root provenance" };
+ }
+ const phase = safeString(activeState.phase).trim().toLowerCase();
+ if (CODEX_GOAL_CREATE_TOOL_NAMES.has(toolName)) {
+ if (phase !== "planning") {
+ return { allowed: false, detail: "create_goal is authorized only during Ultragoal planning" };
+ }
+ if (!exactObjectHasOnlyKeys(toolInput, new Set(["objective", "token_budget"]))) {
+ return { allowed: false, detail: "create_goal payload contains unknown fields or is not an object" };
+ }
+ if (safeString(toolInput.objective).trim() === "") {
+ return { allowed: false, detail: "create_goal requires a non-empty objective" };
+ }
+ if (
+ Object.prototype.hasOwnProperty.call(toolInput, "token_budget")
+ && (!Number.isInteger(toolInput.token_budget) || Number(toolInput.token_budget) <= 0)
+ ) {
+ return { allowed: false, detail: "create_goal token_budget must be a positive integer" };
+ }
+ return { allowed: true };
+ }
+ if (CODEX_GOAL_UPDATE_TOOL_NAMES.has(toolName)) {
+ if (!exactObjectHasOnlyKeys(toolInput, new Set(["status"])) || typeof toolInput.status !== "string") {
+ return { allowed: false, detail: "update_goal payload must contain only an exact status" };
+ }
+ if (toolInput.status === "complete" && phase === "checkpointing") return { allowed: true };
+ if (toolInput.status === "blocked" && phase === "blocked") return { allowed: true };
+ return {
+ allowed: false,
+ detail: `update_goal status ${safeString(toolInput.status) || "<missing>"} is not authorized during Ultragoal ${phase || "active"}`,
+ };
+ }
+ return { allowed: false, detail: `${toolName || "unknown tool"} is not an exact Codex goal mutation tool` };
+}
+
+function nativeAssignmentTargetIsWithinScope(
+ cwd: string,
+ rawTarget: string,
+ pathPrefixes: readonly string[],
+): boolean {
+ const relativeTarget = normalizePlanningArtifactRelativePath(cwd, rawTarget);
+ if (!relativeTarget || conductorPathnameExpansionIsAmbiguous(relativeTarget)) return false;
+ if (conductorPathTraversesLink(cwd, relativeTarget)) return false;
+ return pathPrefixes.some((prefix) => (
+ prefix === "."
+ || relativeTarget === prefix
+ || relativeTarget.startsWith(`${prefix}/`)
+ ));
+}
+
+function classifyNativeAssignedBashAction(command: string): NativeAssignmentAction {
+ const words = tokenizeConductorShellWords(normalizeShellLineContinuations(command))
+ .map(shellWordLiteral)
+ .filter(Boolean);
+ const commandIndex = words.findIndex((word) => !isEnvironmentAssignmentWord(word));
+ const commandName = commandNameFromShellWord(words[commandIndex] ?? "");
+ const firstArg = safeString(words[commandIndex + 1]).trim().toLowerCase();
+ const secondArg = safeString(words[commandIndex + 2]).trim().toLowerCase();
+ if ((commandName === "arc" || commandName === "git") && firstArg === "commit") return "vcs-commit";
+ if ((commandName === "arc" || commandName === "git") && firstArg === "push") return "vcs-push";
+ if (commandName === "gh" && firstArg === "pr") return "pr-update";
+ if (commandName === "ya" && firstArg === "tool" && secondArg === "arcanum") return "pr-update";
+ if (["ssh", "scp", "rsync", "curl", "kubectl", "podman", "docker"].includes(commandName)) {
+ return "live-operation";
+ }
+ return "bash-write";
+}
+
+function nativeAssignedBashHasUnsafeStructure(cwd: string, command: string): boolean {
+ const normalized = normalizeShellLineContinuations(command);
+ if (
+ splitConductorShellSegments(stripHeredocBodiesForCommandScan(normalized)).length !== 1
+ || hasUnresolvedShellArithmeticExpansion(normalized)
+ || hasUnquotedShellSubstitution(normalized)
+ || hasDynamicNestedShellExecution(normalized)
+ || hasUnsafeUnquotedHeredocExpansion(normalized)
+ || commandHasUnsafeConductorShellState(normalized, cwd)
+ || conductorCommandMayMutatePathResolution(normalized)
+ ) return true;
+ const interpreterWrites = extractConductorInterpreterWrites(normalized);
+ return interpreterWrites.some((write) => write.unresolved || write.targets.length === 0);
+}
+
+async function nativeChildAssignmentAllowsMutation(
+ payload: CodexHookPayload,
+ cwd: string,
+ stateDir: string,
+ sessionId: string,
+ toolName: string,
+ mutationTransport: PreToolUseMutationTransport,
+): Promise<boolean> {
+ if (
+ mutationTransport === "state"
+ || mutationTransport === "orchestration"
+ || mutationTransport === "goal"
+ || mutationTransport === "unknown"
+ ) {
+ return false;
+ }
+ const assignment = await readNativeSubagentAssignment(payload, cwd, stateDir, sessionId);
+ if (!assignment) return false;
+
+ if (mutationTransport === "path") {
+ if (!assignment.allowed_actions.includes("path-write")) return false;
+ const candidates = collectImplementationToolPathCandidates(
+ payload,
+ toolName,
+ readPreToolUsePathCandidates(payload),
+ );
+ return candidates.length > 0
+ && candidates.every((candidate) => (
+ !isNativeAssignmentPath(stateDir, candidate, cwd)
+ && nativeAssignmentTargetIsWithinScope(cwd, candidate, assignment.path_prefixes)
+ ));
+ }
+ if (mutationTransport !== "bash") return false;
+
+ const command = readPreToolUseCommand(payload);
+ if (nativeAssignedBashHasUnsafeStructure(cwd, command)) return false;
+ const action = classifyNativeAssignedBashAction(command);
+ if (!assignment.allowed_actions.includes(action)) return false;
+ const targets = extractDeepInterviewCommandWriteTargets(command, cwd, cwd);
+ if (targets.some((target) => isNativeAssignmentPath(stateDir, target, cwd))) return false;
+ if (action !== "bash-write") {
+ return assignment.path_prefixes.includes(".") && targets.every((target) => (
+ nativeAssignmentTargetIsWithinScope(cwd, target, assignment.path_prefixes)
+ ));
+ }
+ return targets.length > 0 && targets.every((target) => (
+ nativeAssignmentTargetIsWithinScope(cwd, target, assignment.path_prefixes)
+ ));
+}
+
+function canonicalRootTransportEnvironmentIsSafe(words: readonly string[], sessionId: string): boolean {
+ for (const rawWord of words) {
+ const word = shellWordLiteral(rawWord);
+ if (!isEnvironmentAssignmentWord(word)) break;
+ const name = shellAssignmentName(word);
+ const value = word.slice(word.indexOf("=") + 1);
+ if ((name !== "OMX_SESSION_ID" && name !== "GJC_SESSION_ID") || value !== sessionId) return false;
+ }
+ return true;
+}
+
+function isCanonicalConductorRootTransport(
+ cwd: string,
+ command: string,
+ sessionId: string,
+ activeState: ActiveConductorState,
+): boolean {
+ if (!sessionId || omxStateTransportHasUnsafeRuntimeWrapper(command)) return false;
+ const canonical = canonicalizeOmxStateTransportCommand(command);
+ const normalized = normalizeShellLineContinuations(canonical);
+ const originalWords = tokenizeConductorShellWords(normalizeShellLineContinuations(command));
+ if (!canonicalRootTransportEnvironmentIsSafe(originalWords, sessionId)) return false;
+ const originalLiteralWords = originalWords.map(shellWordLiteral);
+ const securityNormalized = normalized.replace(
+ /^(?:(?:OMX_SESSION_ID|GJC_SESSION_ID)=[^\s]+\s+)*/,
+ "",
+ );
+ if (
+ splitConductorShellSegments(stripHeredocBodiesForCommandScan(normalized)).length !== 1
+ || hasUnsafeUnquotedHeredocExpansion(normalized)
+ || hasUnquotedShellSubstitution(normalized)
+ || hasDynamicNestedShellExecution(normalized)
+ || hasUnresolvedShellArithmeticExpansion(normalized)
+ || commandHasUnsafeConductorShellState(securityNormalized, cwd)
+ || conductorCommandMayMutatePathResolution(securityNormalized)
+ || extractDeepInterviewCommandRedirectTargets(normalized).length > 0
+ ) return false;
+
+ const words = tokenizeConductorShellWords(normalized);
+ if (!canonicalRootTransportEnvironmentIsSafe(words, sessionId)) return false;
+ const literalWords = words.map(shellWordLiteral);
+ const commandIndex = literalWords.findIndex((word) => !isEnvironmentAssignmentWord(word));
+ if (commandNameFromShellWord(literalWords[commandIndex] ?? "") !== "omx") return false;
+ const surface = safeString(literalWords[commandIndex + 1]).trim();
+ const operation = safeString(literalWords[commandIndex + 2]).trim();
+
+ if (surface === "state") {
+ if (!["read", "write", "clear"].includes(operation)) return false;
+ const allowedFlags = new Set(["--input", "--input-file", "--mode", "--json"]);
+ for (let index = commandIndex + 3; index < literalWords.length; index += 1) {
+ const word = literalWords[index] ?? "";
+ if (word === "--json") continue;
+ const flag = word.includes("=") ? word.slice(0, word.indexOf("=")) : word;
+ if (!allowedFlags.has(flag)) return false;
+ if (!word.includes("=") && flag !== "--json") index += 1;
+ }
+ if (operation === "write") {
+ const payload = readStateWriteInputPayload(cwd, canonical, command);
+ const operations = collectOmxStateCommandOperations(canonical, "write");
+ const stateWrite = operations[0];
+ return operations.length === 1
+ && Boolean(stateWrite)
+ && payload !== null
+ && stateWrite?.nested !== true
+ && !hasPriorExecutableCommand(stateWrite?.prefix ?? "")
+ && safeString(payload?.session_id).trim() === sessionId
+ && suppliedSessionAliasesMatch(payload, sessionId)
+ && safeString(payload?.workingDirectory).trim() !== ""
+ && canonicalDirectoriesEqual(safeString(payload?.workingDirectory), cwd)
+ && conductorStatePayloadPreservesActiveGuard(payload, activeState);
+ }
+ const mode = readStateWriteFlagValue(literalWords, "--mode");
+ if (safeString(mode).trim() !== activeState.mode) return false;
+ if (operation === "clear") {
+ return originalLiteralWords.some((word) => (
+ word === `OMX_SESSION_ID=${sessionId}` || word === `GJC_SESSION_ID=${sessionId}`
+ ));
+ }
+ return true;
+ }
+
+ if (surface !== "ultragoal" || operation !== "checkpoint" || activeState.mode !== "ultragoal") return false;
+ const allowedValueFlags = new Set([
+ "--goal-id",
+ "--status",
+ "--evidence",
+ "--codex-goal-json",
+ "--quality-gate-json",
+ ]);
+ const values = new Map<string, string>();
+ for (let index = commandIndex + 3; index < literalWords.length; index += 1) {
+ const word = literalWords[index] ?? "";
+ if (word === "--json") continue;
+ const equals = word.indexOf("=");
+ const flag = equals >= 0 ? word.slice(0, equals) : word;
+ if (!allowedValueFlags.has(flag)) return false;
+ const value = equals >= 0 ? word.slice(equals + 1) : literalWords[++index] ?? "";
+ if (!value || values.has(flag)) return false;
+ values.set(flag, value);
+ }
+ return /^[A-Za-z0-9._:-]+$/.test(values.get("--goal-id") ?? "")
+ && ["complete", "failed", "blocked"].includes(values.get("--status") ?? "");
+}
+
function buildConductorSessionProvenanceDeny(
activeState: ActiveConductorState,
@@ -18062,6 +18638,12 @@ export async function buildConductorPreToolUseWriteGuardOutput(
const pathCandidates = readPreToolUsePathCandidates(payload);
const mutationTransport = classifyPreToolUseMutationTransport(payload, toolName, cwd);
+ if (
+ writeActor === "main-root"
+ && toolName === "Bash"
+ && isCanonicalConductorRootTransport(policyCwd, command, sessionId, activeState)
+ ) return null;
+
let blocked = false;
let blockedDetail = "Main-root Conductor write is not delegated";
let nativeChildMutationAttempt = false;
@@ -18104,6 +18686,16 @@ export async function buildConductorPreToolUseWriteGuardOutput(
blocked = true;
blockedDetail = "Structured state writes must preserve the canonical active Conductor guard";
}
+ } else if (mutationTransport === "goal") {
+ nativeChildMutationAttempt = true;
+ const goalDecision = validateStandaloneUltragoalGoalMutation(
+ toolName,
+ payload.tool_input,
+ activeState,
+ writeActor,
+ );
+ blocked = !goalDecision.allowed;
+ blockedDetail = goalDecision.detail ?? blockedDetail;
} else if (mutationTransport === "orchestration") {
nativeChildMutationAttempt = true;
} else if (mutationTransport === "path") {
@@ -18113,7 +18705,12 @@ export async function buildConductorPreToolUseWriteGuardOutput(
blocked = true;
blockedDetail = describeConductorBlockedWrite(toolName, undefined, toolPathCandidates.length);
} else {
- const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataExecutionPath(cwd, policyCwd, candidate));
+ const blockedPath = toolPathCandidates.find((candidate) => !isAllowedConductorMetadataExecutionPath(
+ cwd,
+ policyCwd,
+ candidate,
+ sessionId,
+ ));
blocked = blockedPath !== undefined;
if (blockedPath !== undefined) {
blockedDetail = describeConductorBlockedWrite(toolName, blockedPath, toolPathCandidates.length);
@@ -18133,11 +18730,23 @@ export async function buildConductorPreToolUseWriteGuardOutput(
stateDir,
policyCwd,
);
- if (writeActor === "team-worker" && !teamWorkerProtectedStateTarget) return null;
+ if (writeActor === "team-worker" && mutationTransport !== "goal" && !teamWorkerProtectedStateTarget) return null;
if (writeActor === "team-worker" && teamWorkerProtectedStateTarget && !blocked) {
blocked = true;
blockedDetail = "Bash targets protected workflow state outside authorized Team-worker scope";
}
+ if (
+ writeActor === "native-child"
+ && nativeChildMutationAttempt
+ && await nativeChildAssignmentAllowsMutation(
+ payload,
+ policyCwd,
+ stateDir,
+ sessionId,
+ toolName,
+ mutationTransport,
+ )
+ ) return null;
if (!blocked && (writeActor !== "native-child" || !nativeChildMutationAttempt)) return null;
if (!blocked && nativeChildMutationAttempt && writeActor === "native-child") {
blockedDetail = toolName === "Bash"
@@ -20259,7 +20868,13 @@ export async function dispatchCodexNativeHook(
preToolUseSessionId,
policyCwd,
)
- ?? buildRawProtectedWorkflowStatePathOutput(payload, policyCwd, stateDir)
+ ?? await buildRawProtectedWorkflowStatePathOutput(
+ payload,
+ policyCwd,
+ stateDir,
+ preToolUseSessionId,
+ guardedConductorState !== null,
+ )
?? await buildNativeSubagentCapacityCloseGuardOutput(payload, policyCwd, stateDir)
?? buildMalformedPreToolUseBlockTestOutput(payload)
?? buildNativePreToolUseOutput(payload);
diff --git a/docs/native-subagent-assignments.md b/docs/native-subagent-assignments.md
new file mode 100644
index 00000000..cb72befc
--- /dev/null
+++ b/docs/native-subagent-assignments.md
@@ -0,0 +1,84 @@
+# Native subagent assignments
+
+The Codex native hook keeps typed native children read-only unless the active
+root Conductor creates an exact, session-scoped assignment. This transport does
+not grant Team-worker identity or authority.
+
+## Location
+
+For child `<child-agent-id>` in root session `<session-id>`:
+
+```text
+.omx/state/sessions/<session-id>/native-assignments/<encodeURIComponent(child-agent-id)>.json
+```
+
+The root Conductor is the only actor allowed to create or replace this metadata.
+A native child cannot mutate any `native-assignments` path, even when its
+ordinary path scope would otherwise match.
+
+## Schema
+
+The JSON object must contain exactly these keys:
+
+```json
+{
+ "schema_version": 1,
+ "kind": "native-subagent-assignment",
+ "lifecycle": "active",
+ "root_session_id": "exact-root-session-id",
+ "root_thread_id": "exact-root-thread-id",
+ "child_agent_id": "exact-canonical-child-agent-id",
+ "agent_type": "executor",
+ "allowed_actions": ["path-write", "bash-write"],
+ "path_prefixes": ["tmp/native-probe"],
+ "issued_at": "2026-07-23T20:00:00.000Z",
+ "expires_at": "2026-07-23T20:10:00.000Z"
+}
+```
+
+The hook checks the root session and leader thread against the active session
+and native subagent tracker. The child must be tracked as `kind: "subagent"`
+and must provide one unambiguous installed top-level hook role through the
+native `agent_role`/`agent_type` field aliases; the assignment `agent_type`
+must match that role exactly. Real native tracker entries may omit `mode`; when
+tracker `mode` is present, it must also match. Nested-only role evidence or
+conflicting top-level aliases cannot authorize an assignment. The filename
+must encode that same canonical child ID.
+
+Assignments are fail-closed when unreadable, malformed, expired, issued too far
+in the future, longer than 24 hours, symlinked, hard-linked, larger than 64 KiB,
+or writable by group/other. Unknown keys, duplicate actions or prefixes,
+ambiguous paths, symlink traversal, `.omx`, and `.git` scopes are rejected.
+Deleting the file or changing `lifecycle` away from `active` revokes the grant.
+
+## Actions
+
+- `path-write`: direct file-edit tools within `path_prefixes`.
+- `bash-write`: one statically analyzable shell mutation within
+ `path_prefixes`.
+- `vcs-commit`: structural `arc commit` or `git commit`.
+- `vcs-push`: structural `arc push` or `git push`.
+- `pr-update`: structural `gh pr ...` or `ya tool arcanum ...`.
+- `live-operation`: structural `ssh`, `scp`, `rsync`, `curl`, `kubectl`,
+ `podman`, or `docker`.
+
+Operational actions require an explicit `"."` prefix. They do not imply state,
+orchestration, goal, assignment, or Team-worker authority. Shell substitution,
+unresolved arithmetic, dynamic nested execution, unsafe heredocs, unsafe
+nameref/allexport state, arbitrary PATH mutation, unresolved interpreter
+writes, and path/symlink escapes remain blocked.
+
+Without a valid assignment, typed native children continue to receive
+`OWNER_CONFIRMATION_REQUIRED`.
+
+## Root orchestration transports
+
+An active, session-bound Main-root Conductor may write the existing bounded
+metadata roots, including `.omx/state`, `.omx/ultragoal`, `.omx/ralph`,
+`.omx/team`, `.omx/mailbox`, `.omx/handoff`, `.omx/handoffs`, `.omx/goals`,
+`.omx/notepad`, `.omx/wiki`, and `.beads`.
+
+The hook structurally recognizes canonical `omx state read`, `omx state write`,
+`omx state clear`, and `omx ultragoal checkpoint` commands before generic shell
+mutation analysis. Exact session/cwd/mode binding and all existing injection,
+PATH, symlink, and protected-state-gate checks still apply.
diff --git a/src/scripts/__tests__/codex-native-assignment.test.ts b/src/scripts/__tests__/codex-native-assignment.test.ts
new file mode 100644
index 00000000..6db52f0f
--- /dev/null
+++ b/src/scripts/__tests__/codex-native-assignment.test.ts
@@ -0,0 +1,511 @@
+import assert from "node:assert/strict";
+import { realpathSync } from "node:fs";
+import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { dirname, join, resolve } from "node:path";
+import { describe, it } from "node:test";
+import { dispatchCodexNativeHook } from "../codex-native-hook.js";
+
+async function writeJson(path: string, value: unknown): Promise<void> {
+ await mkdir(dirname(path), { recursive: true });
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
+}
+
+async function createActiveConductorFixture(phase = "executing"): Promise<{
+ cwd: string;
+ stateDir: string;
+ sessionId: string;
+ leaderThreadId: string;
+ childAgentId: string;
+}> {
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-assignment-"));
+ const stateDir = join(cwd, ".omx", "state");
+ const sessionId = "sess-native-assignment";
+ const leaderThreadId = "thread-native-assignment-leader";
+ const childAgentId = "agent-native-assignment-executor";
+ const now = new Date().toISOString();
+
+ await writeJson(join(stateDir, "session.json"), {
+ session_id: sessionId,
+ native_session_id: leaderThreadId,
+ cwd,
+ });
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
+ schemaVersion: 1,
+ sessions: {
+ [sessionId]: {
+ session_id: sessionId,
+ leader_thread_id: leaderThreadId,
+ updated_at: now,
+ threads: {
+ [leaderThreadId]: {
+ thread_id: leaderThreadId,
+ kind: "leader",
+ first_seen_at: now,
+ last_seen_at: now,
+ turn_count: 1,
+ },
+ [childAgentId]: {
+ thread_id: childAgentId,
+ kind: "subagent",
+ mode: "executor",
+ leader_thread_id: leaderThreadId,
+ first_seen_at: now,
+ last_seen_at: now,
+ turn_count: 1,
+ },
+ },
+ },
+ },
+ });
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
+ active: true,
+ skill: "ultragoal",
+ phase,
+ session_id: sessionId,
+ active_skills: [
+ { skill: "ultragoal", phase, active: true, session_id: sessionId },
+ ],
+ });
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
+ active: true,
+ mode: "ultragoal",
+ current_phase: phase,
+ session_id: sessionId,
+ });
+
+ return { cwd, stateDir, sessionId, leaderThreadId, childAgentId };
+}
+
+function preToolUse(
+ cwd: string,
+ sessionId: string,
+ identity: Record<string, unknown>,
+ toolName: string,
+ toolInput: Record<string, unknown>,
+) {
+ return dispatchCodexNativeHook({
+ hook_event_name: "PreToolUse",
+ cwd,
+ session_id: sessionId,
+ ...identity,
+ tool_name: toolName,
+ tool_input: toolInput,
+ }, { cwd });
+}
+
+describe("native Conductor assignment transport", () => {
+ it("accepts real typed-child provenance when the native tracker omits mode but rejects missing or conflicting role evidence", async () => {
+ const fixture = await createActiveConductorFixture();
+ const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture;
+ const assignmentPath = join(
+ stateDir,
+ "sessions",
+ sessionId,
+ "native-assignments",
+ `${encodeURIComponent(childAgentId)}.json`,
+ );
+ const issuedAt = new Date();
+ const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000);
+ const trackingPath = join(stateDir, "subagent-tracking.json");
+ const trackedChild = {
+ thread_id: childAgentId,
+ kind: "subagent",
+ leader_thread_id: leaderThreadId,
+ first_seen_at: issuedAt.toISOString(),
+ last_seen_at: issuedAt.toISOString(),
+ turn_count: 1,
+ };
+
+ try {
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: issuedAt.toISOString(),
+ expires_at: expiresAt.toISOString(),
+ });
+ await writeJson(trackingPath, {
+ schemaVersion: 1,
+ sessions: {
+ [sessionId]: {
+ session_id: sessionId,
+ leader_thread_id: leaderThreadId,
+ updated_at: issuedAt.toISOString(),
+ threads: {
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" },
+ [childAgentId]: trackedChild,
+ },
+ },
+ },
+ });
+
+ const realTypedChild = await preToolUse(
+ cwd,
+ sessionId,
+ { agent_id: childAgentId, agent_type: "executor" },
+ "Write",
+ { file_path: "src/assigned/real-typed-child.txt", content: "ok\n" },
+ );
+ assert.equal(realTypedChild.outputJson, null);
+
+ const missingTypedRole = await preToolUse(
+ cwd,
+ sessionId,
+ {
+ agent_id: childAgentId,
+ source: { subagent: { thread_spawn: { agent_role: "executor" } } },
+ },
+ "Write",
+ { file_path: "src/assigned/missing-role.txt", content: "must-not-write\n" },
+ );
+ assert.equal(missingTypedRole.outputJson?.decision, "block");
+ assert.match(String(missingTypedRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+
+ const conflictingTypedAliases = await preToolUse(
+ cwd,
+ sessionId,
+ { agent_id: childAgentId, agent_role: "executor", agent_type: "writer" },
+ "Write",
+ { file_path: "src/assigned/conflicting-aliases.txt", content: "must-not-write\n" },
+ );
+ assert.equal(conflictingTypedAliases.outputJson?.decision, "block");
+ assert.match(String(conflictingTypedAliases.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+
+ await writeJson(trackingPath, {
+ schemaVersion: 1,
+ sessions: {
+ [sessionId]: {
+ session_id: sessionId,
+ leader_thread_id: leaderThreadId,
+ updated_at: issuedAt.toISOString(),
+ threads: {
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader" },
+ [childAgentId]: { ...trackedChild, mode: "writer" },
+ },
+ },
+ },
+ });
+ const conflictingTrackerRole = await preToolUse(
+ cwd,
+ sessionId,
+ { agent_id: childAgentId, agent_role: "executor" },
+ "Write",
+ { file_path: "src/assigned/conflicting-role.txt", content: "must-not-write\n" },
+ );
+ assert.equal(conflictingTrackerRole.outputJson?.decision, "block");
+ assert.match(String(conflictingTrackerRole.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+ } finally {
+ await rm(cwd, { recursive: true, force: true });
+ }
+ });
+
+ it("allows only an exact live typed-child path grant and keeps all negative contracts fail-closed", async () => {
+ const fixture = await createActiveConductorFixture();
+ const { cwd, stateDir, sessionId, leaderThreadId, childAgentId } = fixture;
+ const assignmentPath = join(
+ stateDir,
+ "sessions",
+ sessionId,
+ "native-assignments",
+ `${encodeURIComponent(childAgentId)}.json`,
+ );
+ const issuedAt = new Date();
+ const expiresAt = new Date(issuedAt.getTime() + 10 * 60_000);
+
+ try {
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: issuedAt.toISOString(),
+ expires_at: expiresAt.toISOString(),
+ });
+
+ const exactChild = { agent_id: childAgentId, agent_role: "executor" };
+ const allowed = await preToolUse(
+ cwd,
+ sessionId,
+ exactChild,
+ "Write",
+ { file_path: "src/assigned/result.txt", content: "ok\n" },
+ );
+ assert.equal(allowed.outputJson, null);
+
+ for (const [name, identity, filePath] of [
+ ["outside-scope", exactChild, "src/outside.txt"],
+ ["assignment-self-mutation", exactChild, assignmentPath],
+ ["foreign-child", { agent_id: "agent-native-assignment-foreign", agent_role: "executor" }, "src/assigned/result.txt"],
+ ["role-mismatch", { agent_id: childAgentId, agent_role: "writer" }, "src/assigned/result.txt"],
+ ["ungranted-writer", { agent_id: "agent-native-assignment-writer", agent_role: "writer" }, "src/assigned/result.txt"],
+ ] as const) {
+ const denied = await preToolUse(
+ cwd,
+ sessionId,
+ identity,
+ "Write",
+ { file_path: filePath, content: `${name}\n` },
+ );
+ assert.equal(denied.outputJson?.decision, "block", name);
+ assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name);
+ }
+
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: "foreign-session",
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: issuedAt.toISOString(),
+ expires_at: expiresAt.toISOString(),
+ unexpected_scope_expansion: true,
+ });
+ const malformed = await preToolUse(
+ cwd,
+ sessionId,
+ exactChild,
+ "Write",
+ { file_path: "src/assigned/malformed.txt", content: "malformed\n" },
+ );
+ assert.equal(malformed.outputJson?.decision, "block");
+ assert.match(String(malformed.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+
+ await mkdir(join(cwd, "src", "assigned"), { recursive: true });
+ const escapedTarget = await mkdtemp(join(tmpdir(), "omx-native-assignment-escape-"));
+ await symlink(escapedTarget, join(cwd, "src", "assigned", "link"));
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write", "bash-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: issuedAt.toISOString(),
+ expires_at: expiresAt.toISOString(),
+ });
+ const allowedBash = await preToolUse(
+ cwd,
+ sessionId,
+ exactChild,
+ "Bash",
+ { command: "printf ok > src/assigned/bash.txt" },
+ );
+ assert.equal(allowedBash.outputJson, null);
+ for (const [name, command] of [
+ ["bash-outside-scope", "printf no > src/outside-bash.txt"],
+ ["bash-symlink-escape", "printf no > src/assigned/link/escaped.txt"],
+ ["bash-substitution-poison", "printf \"$(printf no)\" > src/assigned/poison.txt"],
+ ] as const) {
+ const denied = await preToolUse(cwd, sessionId, exactChild, "Bash", { command });
+ assert.equal(denied.outputJson?.decision, "block", name);
+ assert.match(String(denied.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/, name);
+ }
+ await rm(escapedTarget, { recursive: true, force: true });
+
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: new Date(issuedAt.getTime() - 20 * 60_000).toISOString(),
+ expires_at: new Date(issuedAt.getTime() - 10 * 60_000).toISOString(),
+ });
+ const stale = await preToolUse(
+ cwd,
+ sessionId,
+ exactChild,
+ "Write",
+ { file_path: "src/assigned/stale.txt", content: "stale\n" },
+ );
+ assert.equal(stale.outputJson?.decision, "block");
+ assert.match(String(stale.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+
+ await writeJson(assignmentPath, {
+ schema_version: 1,
+ kind: "native-subagent-assignment",
+ lifecycle: "active",
+ root_session_id: sessionId,
+ root_thread_id: leaderThreadId,
+ child_agent_id: childAgentId,
+ agent_type: "executor",
+ allowed_actions: ["path-write"],
+ path_prefixes: ["src/assigned"],
+ issued_at: issuedAt.toISOString(),
+ expires_at: expiresAt.toISOString(),
+ });
+ await chmod(assignmentPath, 0o666);
+ const writableByOthers = await preToolUse(
+ cwd,
+ sessionId,
+ exactChild,
+ "Write",
+ { file_path: "src/assigned/world-writable-grant.txt", content: "must-not-write\n" },
+ );
+ assert.equal(writableByOthers.outputJson?.decision, "block");
+ assert.match(String(writableByOthers.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+ } finally {
+ await rm(cwd, { recursive: true, force: true });
+ }
+ });
+
+ it("recognizes root metadata and canonical state/checkpoint transports before generic shell mutation analysis", async () => {
+ const fixture = await createActiveConductorFixture();
+ const { cwd, sessionId, leaderThreadId } = fixture;
+ const packageCli = realpathSync(resolve(process.cwd(), "dist", "cli", "omx.js"));
+ const packageBin = join(cwd, "node_modules", ".bin", "omx");
+ const inheritedPath = process.env.PATH;
+
+ try {
+ await mkdir(dirname(packageBin), { recursive: true });
+ await symlink(packageCli, packageBin);
+ process.env.PATH = `${dirname(packageBin)}:/usr/bin:/bin`;
+ const root = { agent_id: leaderThreadId, thread_id: leaderThreadId };
+
+ const rootAssignment = await preToolUse(cwd, sessionId, root, "Write", {
+ file_path: `.omx/state/sessions/${sessionId}/native-assignments/root-created.json`,
+ content: "{}\n",
+ });
+ assert.equal(rootAssignment.outputJson, null, "root-assignment-create");
+ const foreignSessionAssignment = await preToolUse(cwd, sessionId, root, "Write", {
+ file_path: ".omx/state/sessions/foreign-session/native-assignments/root-created.json",
+ content: "{}\n",
+ });
+ assert.equal(foreignSessionAssignment.outputJson?.decision, "block", "foreign-session-assignment");
+ const protectedGate = await preToolUse(cwd, sessionId, root, "Write", {
+ file_path: `.omx/state/sessions/${sessionId}/ultragoal-state.json`,
+ content: "{}\n",
+ });
+ assert.equal(protectedGate.outputJson?.decision, "block", "protected-state-gate");
+
+ for (const prefix of [
+ ".omx/state",
+ ".omx/ultragoal",
+ ".omx/ralph",
+ ".omx/team",
+ ".omx/mailbox",
+ ".omx/handoff",
+ ".omx/handoffs",
+ ".omx/goals",
+ ".omx/notepad",
+ ".omx/wiki",
+ ".beads",
+ ]) {
+ const metadata = await preToolUse(
+ cwd,
+ sessionId,
+ root,
+ "Write",
+ { file_path: `${prefix}/native-assignment-regression.json`, content: "{}\n" },
+ );
+ assert.equal(metadata.outputJson, null, prefix);
+ }
+
+ const stateWritePayload = JSON.stringify({
+ mode: "ultragoal",
+ active: true,
+ current_phase: "executing",
+ session_id: sessionId,
+ workingDirectory: cwd,
+ });
+ for (const [name, command] of [
+ ["state-read", "omx state read --mode ultragoal --json"],
+ ["state-write", `omx state write --input '${stateWritePayload}' --json`],
+ ["state-clear", `OMX_SESSION_ID=${sessionId} omx state clear --mode ultragoal --json`],
+ [
+ "ultragoal-checkpoint",
+ "omx ultragoal checkpoint --goal-id G001-regression --status blocked --evidence 'authorization regression' --codex-goal-json '{\"status\":\"in_progress\"}' --json",
+ ],
+ ] as const) {
+ const result = await preToolUse(cwd, sessionId, root, "Bash", { command });
+ assert.equal(result.outputJson, null, name);
+ }
+
+ for (const [name, command] of [
+ ["path-poison", `PATH=${join(cwd, "src")} omx state read --mode ultragoal --json`],
+ ["substitution-poison", "omx state read --mode \"$(printf ultragoal)\" --json"],
+ ["nameref-poison", "declare -n OMX_SESSION_ID=POISON; omx state read --mode ultragoal --json"],
+ ] as const) {
+ const denied = await preToolUse(cwd, sessionId, root, "Bash", { command });
+ assert.equal(denied.outputJson?.decision, "block", name);
+ }
+ } finally {
+ if (inheritedPath === undefined) delete process.env.PATH;
+ else process.env.PATH = inheritedPath;
+ await rm(cwd, { recursive: true, force: true });
+ }
+ });
+
+ it("preserves exact root-only Codex goal transports without lending them to children", async () => {
+ const planning = await createActiveConductorFixture("planning");
+ try {
+ const root = { agent_id: planning.leaderThreadId, thread_id: planning.leaderThreadId };
+ const child = { agent_id: planning.childAgentId, agent_role: "executor" };
+ const readOnly = await preToolUse(planning.cwd, planning.sessionId, child, "functions.get_goal", {});
+ assert.equal(readOnly.outputJson, null, "get-goal-read-only");
+ const create = await preToolUse(planning.cwd, planning.sessionId, root, "functions.create_goal", {
+ objective: "Verify the exact goal transport",
+ token_budget: 100,
+ });
+ assert.equal(create.outputJson, null, "root-create-goal");
+ const malformedCreate = await preToolUse(planning.cwd, planning.sessionId, root, "functions.create_goal", {
+ objective: "Verify the exact goal transport",
+ extra_authority: true,
+ });
+ assert.equal(malformedCreate.outputJson?.decision, "block", "malformed-create-goal");
+ const childCreate = await preToolUse(planning.cwd, planning.sessionId, child, "functions.create_goal", {
+ objective: "Child must not create a goal",
+ });
+ assert.equal(childCreate.outputJson?.decision, "block", "child-create-goal");
+ assert.match(String(childCreate.outputJson?.reason ?? ""), /OWNER_CONFIRMATION_REQUIRED/);
+ } finally {
+ await rm(planning.cwd, { recursive: true, force: true });
+ }
+
+ const checkpointing = await createActiveConductorFixture("checkpointing");
+ try {
+ const root = { agent_id: checkpointing.leaderThreadId, thread_id: checkpointing.leaderThreadId };
+ const complete = await preToolUse(
+ checkpointing.cwd,
+ checkpointing.sessionId,
+ root,
+ "functions.update_goal",
+ { status: "complete" },
+ );
+ assert.equal(complete.outputJson, null, "root-update-goal-complete");
+ const wrongStatus = await preToolUse(
+ checkpointing.cwd,
+ checkpointing.sessionId,
+ root,
+ "functions.update_goal",
+ { status: "blocked" },
+ );
+ assert.equal(wrongStatus.outputJson?.decision, "block", "wrong-phase-goal-status");
+ } finally {
+ await rm(checkpointing.cwd, { recursive: true, force: true });
+ }
+ });
+});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment