Yeachan-Heo/oh-my-codex · error · Error

invalid detached leader parent environment

Error message

invalid detached leader parent environment

What it means

Thrown when the optional parentEnv map contains an invalid entry: a key failing SHELL_ENV_NAME_PATTERN, a key in the forbidden DETACHED_SESSION_PANE_ENV_KEYS set, a non-string value, or a value containing a NUL byte.

Source

Thrown at src/cli/index.ts:6963

export function decodeDetachedLeaderPayload(encoded: string | undefined): DetachedLeaderPayload {
  if (!encoded || !/^[A-Za-z0-9_-]+$/.test(encoded)) throw new Error("invalid detached leader payload");
  let value: unknown;
  try { value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")); } catch { throw new Error("invalid detached leader payload"); }
  if (!value || typeof value !== "object") throw new Error("invalid detached leader payload");
  const payload = value as Record<string, unknown>;
  const options = payload.preLaunchOptions;
  if (typeof payload.cwd !== "string" || typeof payload.sessionName !== "string" || typeof payload.sessionId !== "string" ||
    typeof payload.codexCmd !== "string" || !options || typeof options !== "object" ||
    typeof (options as Record<string, unknown>).enableNotifyFallbackAuthority !== "boolean" ||
    typeof (options as Record<string, unknown>).worktreeDirty !== "boolean" ||
    typeof (options as Record<string, unknown>).shouldAttach !== "boolean") throw new Error("invalid detached leader payload");
  const notifyTempContract = (options as Record<string, unknown>).notifyTempContract;
  if (notifyTempContract !== undefined && (!notifyTempContract || typeof notifyTempContract !== "object")) throw new Error("invalid detached leader payload");
  const parentEnv = payload.parentEnv;
  if (parentEnv !== undefined && (!parentEnv || typeof parentEnv !== "object" ||
    Object.entries(parentEnv).some(([key, value]) => !SHELL_ENV_NAME_PATTERN.test(key) || DETACHED_SESSION_PANE_ENV_KEYS.has(key) || typeof value !== "string" || value.includes("\0")))) {
    throw new Error("invalid detached leader parent environment");
  }
  return {
    cwd: payload.cwd, sessionName: payload.sessionName, sessionId: payload.sessionId, codexCmd: payload.codexCmd,
    ...(typeof payload.codexHomeOverride === "string" ? { codexHomeOverride: payload.codexHomeOverride } : {}),
    ...(typeof payload.projectLocalCodexHomeForCleanup === "string" ? { projectLocalCodexHomeForCleanup: payload.projectLocalCodexHomeForCleanup } : {}),
    ...(typeof payload.runtimeCodexHomeForCleanup === "string" ? { runtimeCodexHomeForCleanup: payload.runtimeCodexHomeForCleanup } : {}),
    ...(parentEnv ? { parentEnv: parentEnv as Record<string, string> } : {}),
    ...(typeof payload.readyPath === "string" ? { readyPath: payload.readyPath } : {}),
    preLaunchOptions: {
      ...(notifyTempContract ? { notifyTempContract: notifyTempContract as NotifyTempContract } : {}),
      enableNotifyFallbackAuthority: (options as DetachedLeaderPreLaunchOptions).enableNotifyFallbackAuthority,
      worktreeDirty: (options as DetachedLeaderPreLaunchOptions).worktreeDirty,
      shouldAttach: (options as DetachedLeaderPreLaunchOptions).shouldAttach,
    },
  };
}

function parseDetachedHudAuthorityProof(value: unknown): DetachedHudAuthority | undefined {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Filter parentEnv through the same SHELL_ENV_NAME_PATTERN and drop DETACHED_SESSION_PANE_ENV_KEYS before encoding
  2. Coerce all values to strings and strip/reject NUL bytes
  3. Pass only a curated allowlist of env vars instead of the full environment

Example fix

// before
parentEnv: { ...process.env }
// after
parentEnv: Object.fromEntries(Object.entries(process.env).filter(([k, v]) => SHELL_ENV_NAME_PATTERN.test(k) && !DETACHED_SESSION_PANE_ENV_KEYS.has(k) && typeof v === 'string' && !v.includes('\0')))
Defensive patterns

Strategy: validation

Validate before calling

const env: Record<string,string> = {};
for (const [k, v] of Object.entries(process.env)) {
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) continue;
  if (['TMUX_PANE','TMUX_SESSION'].includes(k)) continue;
  if (typeof v !== 'string' || v.includes('\0')) continue;
  env[k] = v;
}

Type guard

function isSafeParentEnv(v: unknown): v is Record<string, string> {
  return !!v && typeof v === 'object' && Object.entries(v).every(([k, val]) =>
    typeof val === 'string' && !val.includes('\0') && /^[A-Za-z_][A-Za-z0-9_]*$/.test(k));
}

Prevention

When it happens

Trigger: payload.parentEnv includes keys with invalid shell names (e.g. containing '=' or leading digits depending on the pattern), reserved pane keys like TMUX_PANE, numeric values, or embedded '\0'.

Common situations: Forwarding the entire process.env verbatim into the detached payload, which picks up odd variables set by CI systems or shells; NUL bytes appear when env values were built from binary buffers.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/fa8adcf72c38fa84. Report an issue: GitHub.