Hmbown/CodeWhale · error · Error
${envelope.error}
Error message
${envelope.error} What it means
Every in-place memory edit (edit/retire/revise) must carry evidence, and normalize_evidence runs that evidence through normalize_note: CR/LF are normalized, lines are trimmed, blank lines dropped, and the result must be non-empty and at most 64 KiB (MAX_NOTE_BYTES). This error means the evidence collapsed to an empty string (it was empty, whitespace-only, or only blank lines) — the size overflow surfaces as a different message. The journal treats an unexplained rewrite of durable context as the exact failure mode it exists to prevent.
Source
Thrown at crates/workflow-js/src/vm.rs:1066
const hostPhase = __workflow_phase;
const hostBudgetTotal = __workflow_budget_total;
const hostBudgetSpent = __workflow_budget_spent;
const hostBudgetRemaining = __workflow_budget_remaining;
const MAX_ITEMS = __MAX_ITEMS__;
const taskErrorText = (err) => String(err && err.message !== undefined ? err.message : err);
const isFatalTaskError = (err) => {
const text = taskErrorText(err);
return text.includes("responseSchema") || text.includes("run cancelled");
};
globalThis.task = async (opts) => {
if (opts === null || typeof opts !== "object") {
throw new TypeError("task(): expected an options object");
}
const envelope = JSON.parse(await hostTask(JSON.stringify(opts)));
if (envelope.error !== undefined) {
throw new Error(envelope.error);
}
return envelope.value;
};
globalThis.parallel = (thunks) => {
if (!Array.isArray(thunks)) {
throw new TypeError("parallel(): expected an array of thunks");
}
if (thunks.length > MAX_ITEMS) {
throw new Error("parallel(): max " + MAX_ITEMS + " items per call");
}
return Promise.all(thunks.map((thunk) => {
try {
return Promise.resolve(typeof thunk === "function" ? thunk() : thunk).catch((err) => {
if (isFatalTaskError(err)) throw err;
hostLog("parallel(): dropped a failed slot as null: " + String((err && err.message) || err));
return null;
});View on GitHub (pinned to 8880682c63)
Solutions
- Pass a concrete, non-blank evidence string describing what changed and why, e.g. "user corrected build command to `pnpm build`".
- If evidence comes from a variable, default it: evidence.is_empty() fallback before calling edit.
- Check for the 64 KiB cap too — normalize_evidence shares MAX_NOTE_BYTES with notes, so trim oversized evidence.
Example fix
// before
store.edit(scope, id, from, to, "")?;
// after
let evidence = evidence.trim();
let evidence = if evidence.is_empty() { "manual correction during review" } else { evidence };
store.edit(scope, id, from, to, evidence)?; Defensive patterns
Strategy: validation
Validate before calling
fn usable_evidence(evidence: &str) -> bool {
let collapsed: String = evidence.lines().map(str::trim).filter(|l| !l.is_empty()).collect();
!collapsed.is_empty() && collapsed.len() <= 64 * 1024
}
if !usable_evidence(&evidence) {
anyhow::bail!("provide a short non-empty justification for this memory edit");
}
store.edit(scope, id, from, to, &evidence)?; Type guard
fn is_non_empty_evidence(e: &str) -> bool {
e.lines().any(|l| !l.trim().is_empty())
} Try / catch
match store.edit(scope, id, &from, &to, evidence) {
Ok(hit) => hit,
Err(e) if e.to_string().contains("memory edits require non-empty evidence") =>
store.edit(scope, id, &from, &to, "unspecified manual edit")?,
Err(e) => return Err(e),
} Prevention
- Require a non-empty evidence argument at the tool-call boundary (fail the request before touching the store).
- Never template evidence from possibly-unset variables; use a descriptive constant default.
- Keep evidence under 64 KiB — it shares MAX_NOTE_BYTES with notes.
- Log the evidence alongside the edit so journal audits stay meaningful.
When it happens
Trigger: Calling store.edit(...) or retire(...) with evidence="", evidence=" ", or a string of only newlines/spaces; model tool-calls that fabricate an evidence argument from an empty variable.
Common situations: LLM agents calling the memory-edit tool without filling the evidence parameter; scripts templating evidence from an environment variable that is unset; whitespace introduced by trimming upstream.
Related errors
- ${name} is unavailable in Workflow scripts: runs must be det
- new Date()/Date() is unavailable in Workflow scripts: runs m
- task(): expected an options object
- pipeline(): expected an array of items
- Thread summaries require the runtime bearer token.
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/9305e7d08f278b5d.
Report an issue: GitHub.