{"record":{"id":"c5d6d7b9bb68969b","repo":"ruvnet/ruflo","slug":"invalid-receipt-id","errorCode":null,"errorMessage":"invalid receipt ID","messagePattern":"invalid receipt ID","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-transaction.ts","lineNumber":272,"sourceCode":"        try { fs.unlinkSync(lock); } catch { /* lock already gone */ }\n      }\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n      try {\n        const stat = fs.lstatSync(lock);\n        if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {\n          fs.unlinkSync(lock);\n          continue;\n        }\n      } catch { /* raced with owner */ }\n      if (Date.now() >= deadline) throw new Error('timed out acquiring flywheel transaction lock');\n      await delay(5);\n    }\n  }\n}\n\nfunction validateReceiptId(receiptId: string): void {\n  if (!/^sha256:[a-f0-9]{64}$/.test(receiptId)) throw new Error('invalid receipt ID');\n}\n\nfunction receiptPath(root: string, receiptId: string): string {\n  validateReceiptId(receiptId);\n  return path.join(receiptDir(root), `${receiptId.slice('sha256:'.length)}.json`);\n}\n\nexport function readFlywheelReceipt(root: string, receiptId: string): FlywheelEvaluationReceipt | null {\n  try {\n    const file = receiptPath(root, receiptId);\n    assertSafeFile(file);\n    return JSON.parse(fs.readFileSync(file, 'utf8')) as FlywheelEvaluationReceipt;\n  } catch {\n    return null;\n  }\n}\n\n/**","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/ruvnet/ruflo/blob/5234333c3462640ab348363ba4a142945fd2bc47/v3/@claude-flow/cli/src/services/flywheel-transaction.ts#L254-L290","documentation":"Flywheel evaluation receipts are identified by their content hash. Every function that turns a receipt ID into a filename (`receiptPath`, used by read/consume/verify APIs) first validates it against `^sha256:[a-f0-9]{64}$`. The check is both a format contract and a path-injection guard — a malformed ID can never reach `path.join` and become a traversal vector.","triggerScenarios":"Passing a hash without the `sha256:` prefix, uppercase hex, a truncated or doubly-long hash, or a free-form string (a filename, user input) into `readFlywheelReceipt` or any API taking a receiptId.","commonSituations":"Hand-copied IDs losing the prefix; a database or log pipeline that uppercases or trims values; forwarding unvalidated CLI arguments as receipt IDs; storing receipts keyed by bare hex digest.","solutions":["Pass the exact ID emitted when the receipt was created: `sha256:` plus exactly 64 lowercase hex characters","Normalize before calling: lowercase the value, prepend `sha256:` if missing, and assert the hex part is 64 chars","Validate user-supplied IDs with the same regex before invoking the API"],"exampleFix":"// before: bare digest from your own records\nconst receipt = readFlywheelReceipt(root, '6096e48ef8f2182e0f00348a953f0f00fe0415575b300234fe2316f37b768200');\n\n// after: normalized to the required sha256:… form\nconst id = raw.toLowerCase().startsWith('sha256:') ? raw.toLowerCase() : `sha256:${raw.toLowerCase()}`;\nif (!/^sha256:[a-f0-9]{64}$/.test(id)) throw new Error(`not a receipt id: ${raw}`);\nconst receipt = readFlywheelReceipt(root, id);","handlingStrategy":"type-guard","validationCode":"const RECEIPT_ID_RE = /^sha256:[a-f0-9]{64}$/;\n\nfunction toReceiptId(raw: string): string {\n  const normalized = `sha256:${raw.trim().toLowerCase().replace(/^sha256:/, '')}`;\n  if (!RECEIPT_ID_RE.test(normalized)) {\n    throw new Error(`not a flywheel receipt id: ${raw}`);\n  }\n  return normalized;\n}","typeGuard":"const RECEIPT_ID_RE = /^sha256:[a-f0-9]{64}$/;\n\nfunction isReceiptId(value: unknown): value is `sha256:${string}` {\n  return typeof value === 'string' && RECEIPT_ID_RE.test(value);\n}\n\n// Usage: narrows before the call\nif (!isReceiptId(maybeId)) throw new Error(`invalid receipt id: ${String(maybeId)}`);\nconst receipt = readFlywheelReceipt(root, maybeId);","tryCatchPattern":"try {\n  return readFlywheelReceipt(root, id);\n} catch (e) {\n  if (e?.message === 'invalid receipt ID') {\n    // normalize once (lowercase + sha256: prefix) and retry a single time\n    const normalized = `sha256:${id.trim().toLowerCase().replace(/^sha256:/, '')}`;\n    return /^sha256:[a-f0-9]{64}$/.test(normalized) ? readFlywheelReceipt(root, normalized) : null;\n  }\n  throw e;\n}","preventionTips":["Persist receipt IDs exactly as emitted (sha256: + 64 lowercase hex)","Validate IDs at the system boundary (CLI args, HTTP params) with the same regex","Avoid pipelines that uppercase, trim, or truncate hash strings"],"tags":["validation","id-format","receipts","flywheel"],"backgroundTag":"id-format-validation","analyzedSha":"5234333c3462640ab348363ba4a142945fd2bc47","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}