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

shutdown_config_missing:${config.name}

Error message

shutdown_config_missing:${config.name}

What it means

mutateShutdownConfig re-reads the team config from disk inside the barrier before mutating it; if readTeamConfig returns null the team's config.json no longer exists (or is unreadable/invalid), so there is nothing to mutate and the shutdown sequence aborts. The name suffix identifies which team was being mutated.

Source

Thrown at src/team/runtime.ts:345

  teamName: string,
  cwd: string,
  leaderSessionId?: string,
): Promise<void> {
  await syncExactTeamModeStateOnShutdown(teamName, cwd);
  const normalizedLeaderSessionId = typeof leaderSessionId === 'string' ? leaderSessionId.trim() : '';
  if (normalizedLeaderSessionId) {
    await syncExactTeamModeStateOnShutdown(teamName, cwd, normalizedLeaderSessionId);
  }
}

async function mutateShutdownConfig(
  config: TeamConfig,
  cwd: string,
  mutate: (current: TeamConfig) => void,
): Promise<TeamConfig> {
  return await withTeamTaskMembershipBarrier(config.name, cwd, async () => {
    const current = await readTeamConfig(config.name, cwd);
    if (!current) throw new Error(`shutdown_config_missing:${config.name}`);
    const baseGeneration = current.config_generation ?? 0;
    const teamRoot = join(current.team_state_root ?? resolveCanonicalTeamStateRoot(cwd), 'team', current.name);
    const [currentConfigBytes, currentManifestBytes] = await Promise.all([
      readFile(join(teamRoot, 'config.json'), 'utf8'),
      existsSync(join(teamRoot, 'manifest.v2.json')) ? readFile(join(teamRoot, 'manifest.v2.json'), 'utf8') : null,
    ]);
    mutate(current);
    const currentManifest = currentManifestBytes === null ? null : JSON.parse(currentManifestBytes) as Record<string, unknown>;
    await commitTeamMembershipTaskTransaction(current.name, cwd, {
      baseGeneration,
      tasks: [],
      config: { oldBytes: currentConfigBytes, newBytes: JSON.stringify(current, null, 2) },
      manifest: {
        oldBytes: currentManifestBytes,
        newBytes: currentManifest === null ? null : JSON.stringify({
          ...currentManifest,
          hud_pane_id: current.hud_pane_id,
          hud_pane_pid: current.hud_pane_pid,

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Run `omx team status <name>` to confirm the team state exists in the expected state root
  2. If state was intentionally removed, treat this as already-shutdown and ignore the error
  3. Ensure the cwd passed matches the one the team was started in so the canonical state root resolves identically
Defensive patterns

Strategy: try-catch

Validate before calling

const exists = await readTeamConfig(name, cwd);
if (!exists) { /* treat as already shut down; skip mutation */ }

Try / catch

try { await shutdownTeam(config, cwd); } catch (e) { if (e instanceof Error && e.message.startsWith('shutdown_config_missing:') && !await readTeamConfig(config.name, cwd)) { return; /* already gone */ } throw e; }

Prevention

When it happens

Trigger: Calling shutdownTeam or any path through mutateShutdownConfig after the team state directory was deleted, moved, or its config.json corrupted.

Common situations: Concurrent shutdowns racing to delete state, manual rm of the team state root, an external cleanup job, or cwd mismatch resolving a different state root.

Related errors


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