affaan-m/ECC · error · CapsuleError

capsule.write_failed

capsule.write_failed

Error message

journal write made no progress

What it means

append() writes the canonical JSON entry to the journal with a manual write loop and throws 'capsule.write_failed' if a single fs.writeSync call reports zero or negative bytes written, meaning no forward progress is possible. This guards against infinite loops and silent data loss; the entry was already hashed but is NOT durably recorded, leaving the capsule in a pre-append state for the next verified read.

Solutions

  1. Check free disk space and quotas on the volume holding the capsule directory (df -h, quota).
  2. Retry the append after freeing space; the failed entry was not committed so re-appending is safe.
  3. Check filesystem/dmesg logs for I/O errors on the underlying device.
  4. Move the capsule directory to a local, non-quota-limited filesystem if using network mounts.

Example fix

// before: appending with <1MB free on a tiny tmpfs
await capsule.append('fix', 'note', { msg: 'x' }); // capsule.write_failed

// after: pre-check capacity and surface a clear message
const stat = fs.statfsSync(path.dirname(capsule.journalPath));
if (stat.bavail * stat.bsize < 1024 * 1024) {
  throw new Error('insufficient disk space for capsule journal write');
}
await capsule.append('fix', 'note', { msg: 'x' });
Defensive patterns

Strategy: retry

Validate before calling

const dir = path.dirname(capsule.journalPath);
const st = fs.statfsSync(dir);
if (st.bavail * st.bsize < 5 * 1024 * 1024) throw new Error('low disk space before capsule append');

Try / catch

async function appendWithRetry(capsule, lineage, kind, payload, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await capsule.append(lineage, kind, payload);
    } catch (e) {
      const retryable = e instanceof CapsuleError && e.code === 'capsule.write_failed' && i < attempts - 1;
      if (!retryable) throw e;
      await new Promise(r => setTimeout(r, 2 ** i * 250));
    }
  }
}

Prevention

When it happens

Trigger: fs.writeSync returning <= 0 on the append-mode file descriptor — typically caused by disk-full conditions, EIO from failing storage, hitting a filesystem size limit/quota, or a closed/invalid descriptor in exotic environments.

Common situations: Disk quota exceeded on CI runners; tmpfs or container volume running out of space mid-run; network filesystems returning short/zero writes; the journal hitting a filesystem maximum file size.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

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

        ts: nowIso(this.clock),
        lineage,
        kind,
        effect_class: effectClass,
        harness_version: state.meta.harness_version,
        task_family: state.meta.task_family,
        parent_hash: state.root_hash,
        payload: clean,
      };
      const entry = { ...body, entry_hash: envelope.computeEntryHash(body) };
      const errors = envelope.validateEnvelope(entry);
      if (errors.length > 0) throw new CapsuleError('capsule.invalid_entry', errors.join('; '));
      const bytes = Buffer.from(canonicalJson(entry) + '\n', 'utf8');
      const fd = fs.openSync(this.journalPath, 'a');
      try {
        let offset = 0;
        while (offset < bytes.length) {
          const written = fs.writeSync(fd, bytes, offset, bytes.length - offset, null);
          if (written <= 0) throw new CapsuleError('capsule.write_failed', 'journal write made no progress');
          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) {

View on GitHub (pinned to 8321021c54)