can1357/oh-my-pi · error · ToolError

\`${name}\` cannot run through the eval bridge; call the dir

Error message

\`${name}\` cannot run through the eval bridge; call the direct \`${name}\` tool.

What it means

`checkpoint` and `rewind` are special-cased in `callSessionTool`: the session only honors them as direct toolResult messages, so a bridged call from JS eval would report success without actually taking effect. The bridge deliberately rejects these names with a `ToolError` instructing the caller to use the direct tool instead.

Source

Thrown at packages/coding-agent/src/eval/js/tool-bridge.ts:126

}

export async function callSessionTool(name: string, args: unknown, options: ToolBridgeOptions): Promise<ToolValue> {
	if (name === EVAL_COMPLETION_BRIDGE_NAME) {
		return await runEvalCompletion(args, options);
	}
	if (name === EVAL_AGENT_BRIDGE_NAME) {
		return await runEvalAgent(args, options);
	}
	if (name === EVAL_BUDGET_BRIDGE_NAME) {
		return await runEvalBudget(args, options);
	}
	if (name === EVAL_CONCURRENCY_BRIDGE_NAME) {
		return runEvalConcurrency(args, options);
	}
	if (name === "checkpoint" || name === "rewind") {
		// The session recognizes checkpoint/rewind only as direct toolResult
		// messages; a bridged call would report success without taking effect.
		throw new ToolError(`\`${name}\` cannot run through the eval bridge; call the direct \`${name}\` tool.`);
	}
	const tool = getTool(options.session, name);
	const normalizedArgs = normalizeArgs(args);
	const toolCallId = `js-${name}-${crypto.randomUUID()}`;
	try {
		const result = await tool.execute(
			toolCallId,
			normalizedArgs,
			options.signal,
			undefined,
			options.session.getToolContext?.(),
		);
		const textBlocks = result.content.filter(
			(content): content is { type: "text"; text: string } =>
				content.type === "text" && typeof content.text === "string",
		);
		const imageBlocks = result.content.filter(
			(content): content is { type: "image"; mimeType: string; data: string } =>

View on GitHub (pinned to 9690622007)

Solutions

  1. Don't call checkpoint/rewind from eval code; end the eval run and let the agent emit the direct toolResult message
  2. Restructure the workflow so checkpoint/rewind is triggered by the agent's normal tool call path
  3. If programmatic control is needed, use the SDK/session API that supports checkpoint semantics directly rather than the eval bridge

Example fix

// before (eval code)
await tool("checkpoint", {});
// after: call checkpoint as a direct tool call in the agent loop, not via eval bridge
// or remove the call and let the agent issue the checkpoint itself
Defensive patterns

Strategy: validation

Validate before calling

const BRIDGE_BLOCKED = new Set(["checkpoint", "rewind"]);
if (BRIDGE_BLOCKED.has(name)) {
  throw new Error(`${name} must be issued as a direct tool call, not via the eval bridge`);
}

Try / catch

try {
  await tool("checkpoint", {});
} catch (err) {
  if (err instanceof ToolError && /cannot run through the eval bridge/.test(err.message)) {
    // defer: request the agent to issue checkpoint directly
  }
}

Prevention

When it happens

Trigger: Invoking `tool("checkpoint", ...)` or `tool("rewind", ...)` from within the JS eval bridge runtime.

Common situations: Eval scripts attempting to snapshot or restore session state programmatically, assuming all tools are bridged equally.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a7b76e62a543be63. Report an issue: GitHub.