{"record":{"id":"811b43d3f3359f29","repo":"paperclipai/paperclip","slug":"add-an-objective-after-goal-to-start-a-goal","errorCode":null,"errorMessage":"Add an objective after /goal to start a goal.","messagePattern":"Add an objective after /goal to start a goal\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"info","filePath":"ui/src/components/task-chat/RunnerGoalWidget.tsx","lineNumber":129,"sourceCode":"      setActionError(error instanceof Error ? error.message : \"The goal action could not be applied.\");\n      throw error;\n    }\n  }, [mutation, query]);\n\n  const edit = useCallback(async () => {\n    const current = query.data ?? (await query.refetch()).data;\n    if (!current?.goal) throw new Error(\"There is no current session goal to edit.\");\n    mutation.reset();\n    setActionError(null);\n    setExpanded(true);\n    setDialog({ action: \"edit\", objective: current.goal.objective, revision: current.revision });\n  }, [query, mutation]);\n\n  const executeComposerCommand = useCallback(async (command: RunnerGoalComposerCommand) => {\n    if (command.action === \"focus\") {\n      const current = query.data ?? (await query.refetch()).data;\n      if (!current?.goal && !current?.pendingAction) {\n        throw new Error(\"Add an objective after /goal to start a goal.\");\n      }\n      setExpanded(true);\n      return;\n    }\n    if (command.action === \"edit\") {\n      await edit();\n      return;\n    }\n    if (command.action === \"create\") {\n      const current = query.data ?? (await query.refetch()).data;\n      const unfinished = current?.goal && current.goal.status !== \"complete\";\n      if (unfinished) {\n        mutation.reset();\n        setActionError(null);\n        setExpanded(true);\n        setDialog({ action: \"replace\", objective: command.objective, revision: current.revision });\n      } else {\n        await executeAction(\"create\", command.objective);","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/ui/src/components/task-chat/RunnerGoalWidget.tsx#L111-L147","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Provide an objective text after /goal (e.g. `/goal Ship the fix`) so a goal can be created.","Set the goal via the widget before using focus/edit commands.","Guard the command dispatch: only send `focus` when a goal or pendingAction exists."],"exampleFix":"// before\nawait runnerGoal.executeComposerCommand({ action: \"focus\" });\n// after\nif (await runnerGoal.hasGoal()) {\n  await runnerGoal.executeComposerCommand({ action: \"focus\" });\n} else {\n  composer.showHint(\"Add an objective after /goal to start a goal.\");\n}","handlingStrategy":"validation","validationCode":"const state = await query.refetch().then(r => r.data);\nconst canFocus = !!state?.goal || !!state?.pendingAction;\nif (!canFocus) composerHint(\"Add an objective after /goal to start a goal.\");","typeGuard":"function canFocusGoal(s: { goal?: unknown; pendingAction?: unknown } | null | undefined): boolean {\n  return s != null && (\"goal\" in s && s.goal != null || \"pendingAction\" in s && s.pendingAction != null);\n}","tryCatchPattern":"try {\n  await runnerGoal.executeComposerCommand(cmd);\n} catch (e) {\n  if (e instanceof Error && /no current|Add an objective/.test(e.message)) {\n    composer.showHint(e.message);\n  } else throw e;\n}","preventionTips":["Parse /goal commands with an objective argument requirement and reject empty ones client-side.","Show the empty-goal state in the widget so users know focus is unavailable.","Disable goal commands until the initial goal query resolves."],"tags":["ui","composer-command","empty-state"],"backgroundTag":"empty-result-set","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}