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

  1. Treat it as cancellation: check signal.aborted in the catch block and record a skipped/cancelled tool result
  2. Abort between tool calls instead of mid-call if you need all-or-nothing behavior
  3. 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

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.