affaan-m/ECC · error · CapsuleError

${state.reason}

Error message

${state.reason}

What it means

The public entries() method re-reads and verifies the journal via readJournal(); if the journal is missing, truncated, or fails hash verification it throws a CapsuleError with the read's code, message (state.reason), and failed_at offset. Callers get a precise, diagnostic failure instead of a partially verified entry list, since an unverified journal cannot be trusted.

Solutions

  1. Inspect the error's code and failed_at to locate the first bad byte/entry in the journal.
  2. Repair a torn final line by truncating the incomplete trailing line only if you accept losing that entry, then re-verify.
  3. Restore the journal from a backup or rebuild the capsule if earlier entries fail hash verification.
  4. Always use capsule.entries() (locked/verified) rather than reading the JSONL file directly.

Example fix

// before: reading the raw file and choking on a torn line
const lines = fs.readFileSync('capsule/journal.jsonl', 'utf8').trim().split('\n');

// after: use the verified accessor with explicit failure handling
try {
  const entries = capsule.entries();
} catch (e) {
  console.error(`journal unreadable at ${e.detail?.failed_at}: ${e.message}; restore from backup`);
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

function canReadJournal(capsule) {
  const state = readJournal(capsule.journalPath);
  return state.ok === true;
}

Type guard

function isVerifiedEntryList(state) {
  return state != null && state.ok === true && Array.isArray(state.entries);
}

Try / catch

try {
  const entries = capsule.entries();
} catch (e) {
  if (e instanceof CapsuleError && e.detail?.failed_at != null) {
    console.error(`journal corrupt from offset ${e.detail.failed_at}: ${e.message}`);
    // fall back to last known-good snapshot or abort the report
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling capsule.entries() when the journal file does not exist, ends mid-line (torn write), contains hand-edited lines, or its stored digest/parent-hash chain no longer verifies.

Common situations: Reading entries after a process was killed mid-append (partial last line); inspecting a capsule copied with rsync --partial; someone concatenated or trimmed journal files; concurrent readers observing a file during an external (non-locked) modification.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/e39c1d157262d54c. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/eval-harness/capsule.js:238

          offset += written;
        }
        fs.fsyncSync(fd);
      } finally {
        fs.closeSync(fd);
      }
      // These fields remain observable for compatibility, but are never used as
      // authoritative append state. A preopened handle always reloads above.
      this.meta = state.meta;
      this.lastHash = entry.entry_hash;
      this.nextSeq = entry.seq + 1;
      return entry;
    });
  }

  entries() {
    const state = readJournal(this.journalPath);
    if (!state.ok) {
      throw new CapsuleError(state.code, state.reason, { failed_at: state.failed_at });
    }
    return state.entries;
  }
}

/**
 * Read and verify a journal file. Never throws for content problems; the
 * result names the first failing entry index and a stable reason code.
 */
function readJournal(journalPath) {
  if (!fs.existsSync(journalPath)) {
    return { ok: false, code: 'capsule.missing_journal', reason: 'journal file missing', failed_at: null, entries: [] };
  }
  let bytes;
  try { bytes = fs.readFileSync(journalPath); } catch {
    return { ok: false, code: 'capsule.unreadable_journal', reason: 'journal file could not be read', failed_at: null, entries: [] };
  }
  const raw = bytes.toString('utf8');

View on GitHub (pinned to 8321021c54)