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

legacy dispatch rollback evidence is malformed or…

Error message

legacy dispatch rollback evidence is malformed or unrecognized

What it means

Thrown when the legacy per-team dispatch request file exists but is not a JSON array, or any entry fails strict validation as a rollback-safe TeamDispatchRequest. The file is read during rollback of dispatch requests to discover which legacy request IDs were issued for the workers being removed.

Solutions

  1. Restore or regenerate the per-team dispatch request file from a known-good backup or the authoritative bridge dispatch.json
  2. If the file is disposable compatibility output, delete it (ENOENT returns []) so rollback proceeds with empty legacy evidence
  3. Fix whatever process wrote non-array/non-schema JSON into the path
  4. Check for concurrent unserialized writers to dispatchRequestsPath(teamName, cwd)

Example fix

// before: corrupted file causes throw
// after: allow rollback to proceed with no legacy evidence
try {
  await rollbackDispatch(names);
} catch (e) {
  if (String(e.message).startsWith('legacy dispatch rollback evidence is malformed')) {
    await rm(dispatchRequestsPath(team, cwd), { force: true });
    await rollbackDispatch(names);
  } else throw e;
}
Defensive patterns

Strategy: fallback

Validate before calling

import { readFileSync } from 'node:fs';
function legacyDispatchFileOk(path: string): boolean {
  try {
    const raw = JSON.parse(readFileSync(path, 'utf8'));
    return Array.isArray(raw);
  } catch { return false; }
}

Type guard

function isLegacyDispatchFileViable(path: string): boolean {
  return legacyDispatchFileOk(path);
}

Try / catch

catch (e) { if ((e as Error).message.includes('legacy dispatch rollback evidence')) { /* reset/regenerate legacy file, retry rollback once */ } else throw e; }

Prevention

When it happens

Trigger: Calling the team membership rollback path (removing workers / rolling back dispatch) when .team/<team>/dispatch-requests.json is corrupted, hand-edited, truncated, or contains entries with missing/foreign team_name fields.

Common situations: Concurrent writers corrupted the legacy file; a migration wrote a different schema; partial write due to crash; manual editing of the file.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/team/state.ts:2096

    && request.team_name === teamName
    && typeof request.to_worker === 'string'
    && request.to_worker.length > 0
    && typeof request.trigger_message === 'string'
    && ['hook_preferred_with_fallback', 'transport_direct', 'prompt_stdin'].includes(String(request.transport_preference))
    && typeof request.fallback_allowed === 'boolean'
    && ['pending', 'notified', 'delivered', 'failed'].includes(String(request.status))
    && typeof request.attempt_count === 'number'
    && Number.isFinite(request.attempt_count)
    && typeof request.created_at === 'string'
    && typeof request.updated_at === 'string';
}

async function readLegacyDispatchRequestsForRollback(teamName: string, cwd: string): Promise<TeamDispatchRequest[]> {
  const path = dispatchRequestsPath(teamName, cwd);
  try {
    const raw = JSON.parse(await readFile(path, 'utf8')) as unknown;
    if (!Array.isArray(raw) || raw.some((entry) => !isStrictLegacyDispatchRequestForRollback(entry, teamName))) {
      throw new Error('legacy dispatch rollback evidence is malformed or unrecognized');
    }
    return raw as TeamDispatchRequest[];
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
    throw error;
  }
}

async function writeDispatchRequests(teamName: string, requests: TeamDispatchRequest[], cwd: string): Promise<void> {
  await writeAtomic(dispatchRequestsPath(teamName, cwd), JSON.stringify(requests, null, 2));
  await writeBridgeDispatchCompat(teamName, requests, cwd);
}

function serializeDispatchRequestToBridgeRecord(request: TeamDispatchRequest): DispatchRecord {
  return {
    request_id: request.request_id,
    target: request.to_worker,
    status: request.status,

View on GitHub (pinned to 3ad79a8a6f)