{"record":{"id":"89dad2c89e06e4de","repo":"Yeachan-Heo/oh-my-codex","slug":"frozen-transaction-payload-is-not-the-canonical-st","errorCode":null,"errorMessage":"Frozen transaction payload is not the canonical staged transaction file.","messagePattern":"Frozen transaction payload is not the canonical staged transaction file\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/cli/update-worker.ts","lineNumber":98,"sourceCode":"async function main(): Promise<void> {\n  const payloadPath = process.argv[2];\n  const expectedDigest = process.argv[3];\n  let payload: DeferredUpdatePayload | null = null;\n\n  let stagedDirectory: string | null = null;\n  try {\n    const expectedWorkerDigest = process.argv[4];\n    if (!payloadPath || !expectedDigest || !expectedWorkerDigest) throw new Error('Frozen transaction payload is missing.');\n    const workerPath = await canonicalRegularFile(process.argv[1] ?? '', await realpath(join(process.argv[1] ?? '', '..')));\n    if (!workerPath || digest(await readFile(workerPath, 'utf-8')) !== expectedWorkerDigest) throw new Error('Frozen update worker identity changed before execution.');\n    const stage = await realpath(join(payloadPath, '..'));\n    if (!await ownerOnlyStage(stage) || !basename(stage).startsWith('omx-update-')) {\n      throw new Error('Frozen transaction staging directory is not an owner-only update stage.');\n    }\n    const stagedPayload = await canonicalRegularFile(payloadPath, stage);\n\n    if (!stagedPayload || stagedPayload !== join(stage, 'transaction.json')) {\n      throw new Error('Frozen transaction payload is not the canonical staged transaction file.');\n    }\n    const serialized = await readFile(stagedPayload, 'utf-8');\n    if (digest(serialized) !== expectedDigest) throw new Error('Frozen transaction payload fingerprint changed before execution.');\n    const parsedPayload: unknown = JSON.parse(serialized);\n    if (!isDeferredUpdatePayload(parsedPayload)) throw new Error('Frozen transaction payload is incomplete.');\n    payload = parsedPayload;\n    stagedDirectory = stage;\n    await waitForParent(payload.parentPid);\n    if (!await validatePackageManagerOwnership(payload.ownership)) throw new Error('Frozen manager, package root, or bin ownership validation failed before update.');\n    const result = payload.ownership.manager === 'npm'\n      ? runNpmCommand(payload.ownership.npmCommand, installArgs(payload.ownership), { ...installOptions, env: payload.ownership.environment })\n      : spawnSync(payload.ownership.bunCommand, installArgs(payload.ownership), { ...installOptions, env: payload.ownership.environment });\n    if (result.error || result.status !== 0) throw new Error(String(result.stderr || result.error?.message || 'controller install failed'));\n    const cliEntry = await validatePackageManagerOwnership(payload.ownership);\n    if (!cliEntry) throw new Error('Frozen manager, package root, or bin ownership validation failed after update.');\n    const setup = spawnSync(process.execPath, [cliEntry, ...payload.setupArgs], { cwd: payload.cwd, env: { ...payload.ownership.environment, [SKIP_NATIVE_AGENT_REFRESH_ENV]: '1' }, stdio: 'inherit', windowsHide: true });\n    if (setup.error || setup.status !== 0) throw new Error(setup.error?.message || `setup exited ${setup.status}`);\n    await finalizeSuccessfulUpdate(payload.ownership);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/update-worker.ts#L80-L116","documentation":"The update worker requires the payload argument to canonicalize (via realpath, no symlinks) to exactly <stage>/transaction.json. This error fires when the payload path is a symlink, resolves outside the stage, or is not the canonical transaction.json filename.","triggerScenarios":"Invoking update-worker.js with a payloadPath that is a symbolic link to transaction.json, points to a differently-named file, or resolves to a path other than join(stage, 'transaction.json').","commonSituations":"Manually invoking the worker for debugging with a hand-crafted path, payload replaced by a symlink by backup/sync tooling, or a modified staging layout after a version change.","solutions":["Pass the exact staged path ending in /transaction.json inside the omx-update-* stage directory","Ensure the payload is a regular file, not a symlink (cp -L if copied from elsewhere)","Re-run the standard update flow rather than invoking update-worker.js directly"],"exampleFix":"# before\nnode update-worker.js /tmp/omx-update-abc/txn.json ...\n# after\nnode update-worker.js /tmp/omx-update-abc/transaction.json ...","handlingStrategy":"validation","validationCode":"import { realpath } from 'node:fs/promises';\nimport { join } from 'node:path';\nconst ok = (await realpath(payloadPath)) === join(stage, 'transaction.json');","typeGuard":"const isCanonicalPayload = (p: string, stage: string) => p === join(stage, 'transaction.json');","tryCatchPattern":"Wrap the worker invocation in try-catch and re-run the full update flow (restaging) instead of retrying with the same payload path.","preventionTips":["Never rename or symlink transaction.json inside the stage","Always derive payloadPath as join(stage, 'transaction.json') at spawn time","Don't invoke update-worker.js manually; use the documented update command"],"tags":["update-worker","payload","canonical-path","security"],"backgroundTag":"canonical-path-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}