can1357/oh-my-pi · error · ToolError
No plan is awaiting approval — ${PROPOSE_DEVICE_PATH} only a
Error message
No plan is awaiting approval — ${PROPOSE_DEVICE_PATH} only accepts a plan title while plan mode is active. What it means
dispatchResolutionDevice handles the 'xdev propose' device. The propose path only accepts a plan title while a plan proposal handler is registered (plan mode active). If session.peekPlanProposalHandler() returns undefined — no plan is awaiting approval — it throws this ToolError pointing at the PROPOSE_DEVICE_PATH usage contract.
Source
Thrown at packages/coding-agent/src/tools/resolve.ts:306
}
/**
* Execute a resolution-device write. `text` is the raw plain-text body:
* - `xd://resolve` / `xd://reject` → the reason; dispatches to the pending
* preview invoker (in-flight queue directive first).
* - `xd://propose` → the plan title; dispatches to the plan-proposal handler
* installed by plan mode.
*/
export async function dispatchResolutionDevice(
session: ToolSession,
device: ResolutionDeviceName,
text: string,
): Promise<{ result: AgentToolResult<unknown>; xdev: XdevDispatch }> {
const body = text.trim();
if (device === PROPOSE_DEVICE_NAME) {
const handler = session.peekPlanProposalHandler?.();
if (!handler) {
throw new ToolError(
`No plan is awaiting approval — ${PROPOSE_DEVICE_PATH} only accepts a plan title while plan mode is active.`,
);
}
const result = await handler(body);
return { result, xdev: { tool: device, mode: "execute", args: { title: body }, inner: result.details } };
}
const action: ResolveAction = device === RESOLVE_DEVICE_NAME ? "apply" : "discard";
const xdevBase: XdevDispatch = { tool: device, mode: "execute", args: { reason: body } };
const invoker = session.peekQueueInvoker?.() ?? session.peekPendingInvoker?.();
if (!invoker) {
session.clearPendingInvokers?.();
const proposeHint = session.peekPlanProposalHandler?.()
? ` To submit the plan for approval, write its title to ${PROPOSE_DEVICE_PATH} instead.`
: "";
// Rejecting is a request to reach the "no staged change" end-state, which
// already holds when nothing is pending — honor it as a successful
// cancellation instead of surfacing a hard error. Apply still errors.View on GitHub (pinned to 9690622007)
Solutions
- Enter plan mode / create a plan proposal before using the propose device
- Use the resolve device (RESOLVE_DEVICE_PATH) to approve/reject an existing pending plan instead
- Check the session state: only one plan can await approval at a time
Defensive patterns
Strategy: validation
Validate before calling
if (!session.peekPlanProposalHandler?.()) { throw new Error("Plan mode is not active; cannot use the propose device"); } Type guard
function canPropose(session: ToolSession): boolean { return typeof session.peekPlanProposalHandler?.() === "function"; } Try / catch
try { await proposePlan(title); } catch (e) { if (e instanceof ToolError && e.message.includes("No plan is awaiting approval")) { /* fall back to resolve device or enable plan mode */ } else throw e; } Prevention
- Only invoke propose while plan mode is active with a pending proposal
- Track whether a proposal was already approved/rejected this session
- Prefer the resolve device when a plan already exists
When it happens
Trigger: Invoking the propose device when plan mode is not active, the plan was already approved/rejected, or the session never registered a plan proposal handler.
Common situations: Model tries to propose a plan title outside plan mode; a second propose after one was handled; stale transcript replayed into a new session without plan mode.
Related errors
- Plan mode is not active.
- Plan mode is not active.
- Checkpoint already completed; continue from the retained rew
- No pending action to apply — ${RESOLVE_DEVICE_PATH} is only
- directory stack is empty
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1e89c83be126719d.
Report an issue: GitHub.