Hmbown/CodeWhale · error · Error
Invalid : nonempty bounded text required.
Error message
Invalid ${field}: nonempty bounded text required. What it means
text is the evidence-module validator for string fields: the value must be a non-empty string of at most `max` characters (default 256) containing no control characters (U+0000–U+001F, U+007F). It throws 'Invalid <field>: nonempty bounded text required.' otherwise, protecting identifiers and labels embedded in evidence from being empty, oversized, or containing injection-prone control bytes.
Solutions
- Trim and ensure the field is non-empty before validating.
- Strip control characters (e.g. v.replace(/[\u0000-\u001f\u007f]/g, '')) and truncate to the field's max length.
- For genuinely long content, move it to a field with a larger bound or store it out-of-band with a short reference here.
- Check for ANSI escape/color codes in terminal-derived strings and strip them at the source.
Example fix
// before
validateObservation({ label: process.stderrChunk }) // may contain ANSI/\n
// after
const label = process.stderrChunk.replace(/[\u0000-\u001f\u007f]/g, '').slice(0, 256);
validateObservation({ label }); Defensive patterns
Strategy: validation
Validate before calling
const cleanText = (v, max = 256) =>
typeof v === 'string' ? v.replace(/[\u0000-\u001f\u007f]/g, '').slice(0, max) : '';
if (!cleanText(label)) throw new Error('label is empty after cleaning'); Type guard
const isBoundedText = (v, max = 256) => typeof v === 'string' && v.length > 0 && v.length <= max && !/[\u0000-\u001f\u007f]/.test(v);
Try / catch
try {
validateObservation(observation);
} catch (e) {
if (e.message.includes('nonempty bounded text required')) {
console.error(`${e.message} — strip control chars / enforce max length at the producer`);
} else throw e;
} Prevention
- Sanitize any string derived from logs, terminals, or user input before storing it in evidence.
- Enforce a max-length convention (<= 256) for identifier-like fields at write time.
- Strip ANSI escapes and newlines when converting multi-line output to single-line labels.
- After trimming, check non-emptiness — whitespace-only strings fail validation.
When it happens
Trigger: Calling any validator/builder that reads a text field with an empty string, a string longer than the limit, or one containing \n, \t, ANSI escapes, NUL bytes, or other control characters; passing undefined for a required text field.
Common situations: Multi-line descriptions pasted into single-line label fields; log lines or user input with embedded control characters or ANSI color codes; unbounded identifiers generated upstream; empty values after a trim/strip step removed whitespace.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/c596433e58c6e41e.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/evidence.ts:67
export interface Interaction {
runId: string; sandboxId?: string; group?: string; target: string;
kind: string; stage: Stage; effect: Effect; count: number; recordIds: string[];
}
export interface BoundaryAnalysis {
version: 1; omittedFindings: number; asOf: number; inputRecords: number; visibleRecords: number; uniqueRecords: number;
futureRecords: number; findings: BoundaryFinding[]; coverage: CoverageRow[];
interactions: Interaction[]; runs: number; sandboxes: number; externalEffects: number;
unknownEffects: number; authority: { permitted: number; denied: number; unknown: number };
provenance: string;
}
const enumValue = <T extends string>(v:unknown, values: readonly T[], field:string): T => {
if(typeof v!=='string'||!values.includes(v as T))throw new Error(`Invalid ${field}.`);return v as T;
};
const object = (v:unknown, field:string): Record<string,unknown> => {
if(!v||typeof v!=='object'||Array.isArray(v))throw new Error(`Invalid ${field}: object required.`);return v as Record<string,unknown>;
};
const text = (v:unknown, field:string, max=256):string => {
if(typeof v!=='string'||!v.length||v.length>max||/[\u0000-\u001f\u007f]/.test(v))throw new Error(`Invalid ${field}: nonempty bounded text required.`);return v;
};
const num = (v:unknown,field:string,min=0):number => {
if(typeof v!=='number'||!Number.isFinite(v)||v<min||Math.abs(v)>Number.MAX_SAFE_INTEGER)throw new Error(`Invalid ${field}.`);return v;
};
const optionalText = (o:Record<string,unknown>,key:string):string|undefined => o[key]===undefined?undefined:text(o[key],key);
const strings = (v:unknown,field:string,allowEmpty=false):string[]=>{
if(!Array.isArray(v)||(!allowEmpty&&!v.length)||v.length>256)throw new Error(`Invalid ${field}.`);
const out=v.map(x=>text(x,field));if(new Set(out).size!==out.length)throw new Error(`Duplicate ${field}.`);return out;
};
const boolean = (v:unknown,field:string):boolean=>{if(typeof v!=='boolean')throw new Error(`Invalid ${field}.`);return v;};
const optionalNumber=(o:Record<string,unknown>,key:string)=>o[key]===undefined?undefined:num(o[key],key);
function onlyKeys(o:Record<string,unknown>,keys:string[],label:string):void{
for(const k of Object.keys(o))if(!keys.includes(k))throw new Error(`Unknown ${label} field: ${k}.`);
}
/** All unrecognized fields are rejected, never secretly retained in metadata-only evidence. */
export function validateObservation(input:unknown):Observation {
const o=object(input,'observation');
onlyKeys(o,['version','id','runId','operationId','sourceId','epoch','sequence','time','receivedAt','subject','stage','surface','action','effect','status','target','actionDigest','authority','correlation','facts'],'observation');View on GitHub (pinned to 433685b202)