ruvnet/ruflo · error · Error
invalid receipt ID
Error message
invalid receipt ID
What it means
Thrown by validateReceiptId() when a receipt ID does not match the strict regex ^sha256:[a-f0-9]{64}$. Receipt IDs are content hashes of the canonicalized payload (sha256Ref output). The regex is also a path-traversal guard: it forbids slashes, dots, and uppercase, so a crafted ID cannot escape the receipts/ directory.
Source
Thrown at v3/@claude-flow/cli/src/services/flywheel-transaction.ts:265
try { fs.unlinkSync(lock); } catch { /* lock already gone */ }
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
try {
const stat = fs.lstatSync(lock);
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
fs.unlinkSync(lock);
continue;
}
} catch { /* raced with owner */ }
if (Date.now() >= deadline) throw new Error('timed out acquiring flywheel transaction lock');
await delay(5);
}
}
}
function validateReceiptId(receiptId: string): void {
if (!/^sha256:[a-f0-9]{64}$/.test(receiptId)) throw new Error('invalid receipt ID');
}
function receiptPath(root: string, receiptId: string): string {
validateReceiptId(receiptId);
return path.join(receiptDir(root), `${receiptId.slice('sha256:'.length)}.json`);
}
export function readFlywheelReceipt(root: string, receiptId: string): FlywheelEvaluationReceipt | null {
try {
const file = receiptPath(root, receiptId);
assertSafeFile(file);
return JSON.parse(fs.readFileSync(file, 'utf8')) as FlywheelEvaluationReceipt;
} catch {
return null;
}
}
/**View on GitHub (pinned to 6b01dc5a68)
Solutions
- Always use the receiptId returned by createFlywheelReceipt() (payload.receiptId).
- If constructing manually, format as `sha256:${lowercaseHex64}`.
- Validate with the same regex before calling read APIs: /^sha256:[a-f0-9]{64}$/.test(id).
Example fix
// before readFlywheelReceipt(root, '6096e48ef8f2182e...'); // missing prefix // after readFlywheelReceipt(root, `sha256:6096e48ef8f2182e...`); // or use receipt.payload.receiptId
Defensive patterns
Strategy: validation
Validate before calling
const RECEIPT_ID_RE = /^sha256:[a-f0-9]{64}$/;
function assertValidReceiptId(id: string): void {
if (!RECEIPT_ID_RE.test(id)) {
throw new Error(`receipt ID must be sha256:<64 lowercase hex>, got: ${id}`);
}
}
assertValidReceiptId(receiptId); Type guard
const isValidReceiptId = (x: unknown): x is string => typeof x === 'string' && /^sha256:[a-f0-9]{64}$/.test(x); Try / catch
try {
readFlywheelReceipt(root, id);
} catch (e) {
if (e instanceof Error && e.message === 'invalid receipt ID') {
throw new Error(`rejecting untrusted receipt ID input: ${id}`);
}
throw e;
} Prevention
- Always source receipt IDs from createFlywheelReceipt output (payload.receiptId).
- Never accept raw user input as a receipt ID without regex validation.
- Lowercase hex before formatting if you construct IDs manually.
When it happens
Trigger: Calling readFlywheelReceipt(root, receiptId) or any path that routes through receiptPath() with an ID like 'sha256:ABC...', 'abc123', 'sha256:short', '../evil', or a missing 'sha256:' prefix.
Common situations: Passing a raw hex digest without the 'sha256:' prefix; using uppercase hex (must be lowercase); truncating the hash; a CLI/UX bug that passed user input unvalidated; a path-injection attempt.
Related errors
- basePath contains disallowed characters
- Key contains disallowed characters
- Namespace contains disallowed characters
- flywheel anchor path must stay inside project root
- Validation failed: ${result.error}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/c5d6d7b9bb68969b.
Report an issue: GitHub.