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

  1. Always use the receiptId returned by createFlywheelReceipt() (payload.receiptId).
  2. If constructing manually, format as `sha256:${lowercaseHex64}`.
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/c5d6d7b9bb68969b. Report an issue: GitHub.