paperclipai/paperclip · info

Add an objective after /goal to start a goal.

Error message

Add an objective after /goal to start a goal.

What it means

Thrown by `executeComposerCommand` when a `/goal` composer command with action "focus" is issued but the session has neither an existing goal nor a pending goal action. Focusing the goal widget is meaningless in that state, so the hook rejects with guidance telling the user to add an objective.

Source

Thrown at ui/src/components/task-chat/RunnerGoalWidget.tsx:129

      setActionError(error instanceof Error ? error.message : "The goal action could not be applied.");
      throw error;
    }
  }, [mutation, query]);

  const edit = useCallback(async () => {
    const current = query.data ?? (await query.refetch()).data;
    if (!current?.goal) throw new Error("There is no current session goal to edit.");
    mutation.reset();
    setActionError(null);
    setExpanded(true);
    setDialog({ action: "edit", objective: current.goal.objective, revision: current.revision });
  }, [query, mutation]);

  const executeComposerCommand = useCallback(async (command: RunnerGoalComposerCommand) => {
    if (command.action === "focus") {
      const current = query.data ?? (await query.refetch()).data;
      if (!current?.goal && !current?.pendingAction) {
        throw new Error("Add an objective after /goal to start a goal.");
      }
      setExpanded(true);
      return;
    }
    if (command.action === "edit") {
      await edit();
      return;
    }
    if (command.action === "create") {
      const current = query.data ?? (await query.refetch()).data;
      const unfinished = current?.goal && current.goal.status !== "complete";
      if (unfinished) {
        mutation.reset();
        setActionError(null);
        setExpanded(true);
        setDialog({ action: "replace", objective: command.objective, revision: current.revision });
      } else {
        await executeAction("create", command.objective);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Provide an objective text after /goal (e.g. `/goal Ship the fix`) so a goal can be created.
  2. Set the goal via the widget before using focus/edit commands.
  3. Guard the command dispatch: only send `focus` when a goal or pendingAction exists.

Example fix

// before
await runnerGoal.executeComposerCommand({ action: "focus" });
// after
if (await runnerGoal.hasGoal()) {
  await runnerGoal.executeComposerCommand({ action: "focus" });
} else {
  composer.showHint("Add an objective after /goal to start a goal.");
}
Defensive patterns

Strategy: validation

Validate before calling

const state = await query.refetch().then(r => r.data);
const canFocus = !!state?.goal || !!state?.pendingAction;
if (!canFocus) composerHint("Add an objective after /goal to start a goal.");

Type guard

function canFocusGoal(s: { goal?: unknown; pendingAction?: unknown } | null | undefined): boolean {
  return s != null && ("goal" in s && s.goal != null || "pendingAction" in s && s.pendingAction != null);
}

Try / catch

try {
  await runnerGoal.executeComposerCommand(cmd);
} catch (e) {
  if (e instanceof Error && /no current|Add an objective/.test(e.message)) {
    composer.showHint(e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Executing a `focus` RunnerGoalComposerCommand (e.g. user types /goal in the composer) when `query.data.goal` and `query.data.pendingAction` are both falsy after a refetch.

Common situations: Typing /goal in a brand-new session that has no goal yet; issuing /goal focus after the goal was removed; stale cache hides a goal but refetch confirms none exists.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/811b43d3f3359f29. Report an issue: GitHub.