affaan-m/ECC · error · CapsuleError
capsule.payload_denied
capsule.payload_denied
Error message
payload keys not allowlisted: ${dropped.join(', ')} What it means
redactPayload() drops any payload keys not on the allowlist for the given lineage/kind, returning them in the dropped array. By default (strict mode) append() rejects the call with 'capsule.payload_denied' listing the disallowed keys instead of silently discarding data. Callers may explicitly opt out with options.strict === false to accept key dropping.
Solutions
- Read the dropped list in the message and either remove those keys or rename them to allowlisted ones.
- Set options.strict = false only if silently dropping the extra keys is acceptable.
- If the field is genuinely needed, extend the payload allowlist in envelope.redactPayload for that lineage/kind.
- Project payloads down to the documented minimal schema per entry kind.
Example fix
// before
await capsule.append('fix', 'note', { msg: 'ok', traceId: 'abc' }); // capsule.payload_denied
// after: only allowlisted keys
await capsule.append('fix', 'note', { msg: 'ok traceId=abc' });
// or opt into dropping:
await capsule.append('fix', 'note', { msg: 'ok', traceId: 'abc' }, { strict: false }); Defensive patterns
Strategy: validation
Validate before calling
const { dropped } = envelope.redactPayload(payload, {});
if (dropped.length > 0) console.warn(`keys will be rejected/dropped: ${dropped.join(', ')}`); Type guard
function hasOnlyAllowlistedKeys(payload, allow) {
return Object.keys(payload).every(k => allow.includes(k));
} Try / catch
try {
await capsule.append(lineage, kind, payload);
} catch (e) {
if (e instanceof CapsuleError && e.code === 'capsule.payload_denied') {
const extra = e.detail?.dropped ?? [];
const trimmed = Object.fromEntries(Object.entries(payload).filter(([k]) => !extra.includes(k)));
return capsule.append(lineage, kind, trimmed);
}
throw e;
} Prevention
- Keep payload keys to the documented per-lineage schema.
- When renaming payload fields, update the allowlist in the same PR.
- Never dump whole config/request objects into payloads.
- Decide explicitly whether strict mode should stay on for your workload.
When it happens
Trigger: Calling append() with payload keys that are not allowlisted for that entry type — e.g. adding custom fields like { foo: 1 } to a lineage whose envelope only permits { msg } or defined fields, or renaming a field without updating the allowlist.
Common situations: Adding extra debugging fields 'just this once'; a refactor renaming a payload field (e.g. error -> message) so the old key is no longer allowlisted; passing the whole request/config object as payload.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- capsule.payload_invalid
- invalid interaction id/token
- -32602
- a claim token is required
- a confirmed nonempty coordinate is required
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/d623ebce50861330.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/eval-harness/capsule.js:194
* A partial I/O failure is preserved for diagnosis, never silently rolled back.
*/
append(lineage, kind, payload = {}, options = {}) {
return withAppendLock(this.dir, () => {
const state = readCapsule(this.dir);
if (!state.ok) throw new CapsuleError(state.code, state.reason, { failed_at: state.failed_at });
if (!envelope.LINEAGES.includes(lineage)) {
throw new CapsuleError('capsule.bad_lineage', `unknown lineage ${lineage}`);
}
const effectClass = options.effect_class || 'SE0';
const { payload: clean, dropped, findings, errors: payloadErrors } = envelope.redactPayload(payload, options);
if (payloadErrors.length > 0) {
throw new CapsuleError('capsule.payload_invalid', payloadErrors.join('; '));
}
if (findings.length > 0) {
throw new CapsuleError('capsule.secret_canary', `payload tripped secret canary ${findings[0].canary} at ${findings[0].path}`, { findings });
}
if (dropped.length > 0 && options.strict !== false) {
throw new CapsuleError('capsule.payload_denied', `payload keys not allowlisted: ${dropped.join(', ')}`, { dropped });
}
const body = {
schema: envelope.SCHEMA_VERSION,
run_id: state.meta.run_id,
capsule_id: state.meta.capsule_id,
seq: state.entries.length,
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('; '));View on GitHub (pinned to 8321021c54)