earendil-works/pi · info
Operation aborted
Error message
Operation aborted
What it means
The write harness tool checks its AbortSignal inside the per-file mutation queue and throws this when cancellation was requested before env.writeFile runs (write.ts:29). It is cooperative cancellation, not a fault: the tool deliberately skips the write. The target file is still unmodified at this checkpoint.
Source
Thrown at packages/agent/src/harness/tools/write.ts:29
});
export type WriteToolInput = Static<typeof writeSchema>;
export function createWriteTool<TContext extends ExecutionToolContext = ExecutionToolContext>(): AgentHarnessTool<
TContext,
typeof writeSchema,
undefined
> {
return {
name: "write",
label: "write",
description:
"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
parameters: writeSchema,
async execute(_toolCallId, { path, content }, signal, _onUpdate, { env }) {
const absolutePath = await resolveToolPath(env, path, signal);
return withFileMutationQueue(env, absolutePath, async () => {
if (signal?.aborted) throw new Error("Operation aborted");
getOrThrow(await env.writeFile(absolutePath, content, signal));
if (signal?.aborted) throw new Error("Operation aborted");
return {
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
details: undefined,
};
});
},
};
}
View on GitHub (pinned to 4af9d21d3b)
Solutions
- Treat it as cancellation: check signal.aborted in the catch block and record a skipped/cancelled tool result
- Abort between tool calls instead of mid-call if you need all-or-nothing behavior
- Check that no unrelated AbortController (timeout, unmount, sibling failure) is aborting the shared signal
Example fix
// before
const result = await writeTool.execute(id, input, signal, undefined, ctx);
// after - treat abort as cancellation
try {
const result = await writeTool.execute(id, input, signal, undefined, ctx);
} catch (e) {
if (signal?.aborted) return { content: [{ type: "text", text: "Write cancelled" }], details: undefined };
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (signal?.aborted) {
return { content: [{ type: "text", text: "Write skipped: request already cancelled" }], details: undefined };
}
await writeTool.execute(id, input, signal, undefined, ctx); Type guard
const isAbortError = (e: unknown): boolean => e instanceof Error && e.message === "Operation aborted";
Try / catch
try {
return await writeTool.execute(id, input, signal, undefined, ctx);
} catch (e) {
if (signal?.aborted && e instanceof Error && e.message === "Operation aborted") {
return cancelledResult(); // file untouched at this checkpoint
}
throw e;
} Prevention
- Check signal.aborted before invoking the tool
- Abort between tool calls for all-or-nothing semantics
- Keep AbortController ownership explicit and single-purpose
When it happens
Trigger: Calling the write tool's execute() with a signal whose AbortController fires during resolveToolPath or while entering withFileMutationQueue - user pressed stop, a timeout controller expired, or a shared batch signal was aborted by a sibling failure.
Common situations: Stop buttons wired to the tool-execution signal; request-scoped controllers aborted on component unmount; fail-fast parallel tool batches sharing one signal.
Related errors
AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24).
Data as JSON: /api/errors/1059fb8f551ed41f.
Report an issue: GitHub.