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.

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.

Appendix: 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)