jd-opensource/joyagent-jdgenie · error · IllegalStateException

No plan exists. Create a plan first.

Error message

No plan exists. Create a plan first.

What it means

PlanningTool maintains an in-memory plan object for an agent session. updatePlan throws this IllegalStateException when plan update/revise is invoked but no plan was ever created via the create command. The tool enforces plan lifecycle ordering: create must precede update and mark_step.

Solutions

  1. Have the agent call the create command first before issuing update_plan.
  2. Auto-create a default plan in updatePlan if plan == null (defensive fallback).
  3. Persist the plan across tool instances/sessions instead of keeping it in a field.

Example fix

// before
if (plan == null) {
    throw new IllegalStateException("No plan exists. Create a plan first.");
}
// after
if (plan == null) {
    plan = new Plan(title, steps); // auto-create
    return "我已创建plan";
}
plan.update(title, steps);
Defensive patterns

Strategy: validation

Validate before calling

// caller-side check before invoking update
if (!planningTool.hasPlan()) {
    planningTool.execute(Map.of("command", "create", "title", t, "steps", steps));
}

Try / catch

try {
    tool.execute(params);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("No plan exists")) {
        tool.execute(createParams); // then retry update
    } else throw e;
}

Prevention

When it happens

Trigger: Calling PlanningTool with command=update (or any non-create command) when the internal `plan` field is null — i.e., `create` was never invoked in this session, or a new tool/agent instance replaced the one holding the plan.

Common situations: Agent LLM emits an update_plan action before ever creating a plan; agent restart or session switch loses the in-memory plan while the prompt history still shows plan steps; multiple PlanningTool instances each holding separate state.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of jd-opensource/joyagent-jdgenie@2417e0b8b6 (2026-09-08). Data as JSON: /api/errors/f10d99a8eb847186. Report an issue: GitHub.

Appendix: source

Thrown at genie-backend/src/main/java/com/jd/genie/agent/tool/common/PlanningTool.java:161

        if (title == null || steps == null) {
            throw new IllegalArgumentException("title, and steps are required for create command");
        }

        if (plan != null) {
            throw new IllegalStateException("A plan already exists. Delete the current plan first.");
        }

        plan = Plan.create(title, steps);
        return "我已创建plan";
    }

    private String updatePlan(Map<String, Object> params) {
        String title = (String) params.get("title");
        List<String> steps = (List<String>) params.get("steps");

        if (plan == null) {
            throw new IllegalStateException("No plan exists. Create a plan first.");
        }

        plan.update(title, steps);
        return "我已更新plan";
    }

    private String markStep(Map<String, Object> params) {
        Integer stepIndex = (Integer) params.get("step_index");
        String stepStatus = (String) params.get("step_status");
        String stepNotes = (String) params.get("step_notes");

        if (plan == null) {
            throw new IllegalStateException("No plan exists. Create a plan first.");
        }

        if (stepIndex == null) {
            throw new IllegalArgumentException("step_index is required for mark_step command");
        }

View on GitHub (pinned to 2417e0b8b6)