{"record":{"id":"b62d96f895d82a7c","repo":"tinyhumansai/openhuman","slug":"worktreeapi-remove-path-is-required","errorCode":null,"errorMessage":"worktreeApi.remove: path is required","messagePattern":"worktreeApi\\.remove: path is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/src/services/api/worktreeApi.ts","lineNumber":84,"sourceCode":"\n  /** Fetch a human-readable `git diff HEAD --stat` (plus untracked files). */\n  diff: async (path: string): Promise<string> => {\n    if (!path.trim()) throw new Error('worktreeApi.diff: path is required');\n    log('diff path=%s', path);\n    const res = await callCoreRpc<{ summary: string }>({\n      method: 'openhuman.worktree_diff',\n      params: { path },\n    });\n    return res.summary;\n  },\n\n  /**\n   * Remove a worktree checkout. The core refuses a dirty worktree unless\n   * `force` is `true`, so a clean worktree removes silently while a dirty one\n   * rejects (the caller surfaces a confirm prompt and retries with force).\n   */\n  remove: async (path: string, force = false): Promise<boolean> => {\n    if (!path.trim()) throw new Error('worktreeApi.remove: path is required');\n    log('remove path=%s force=%s', path, force);\n    const res = await callCoreRpc<{ removed: boolean }>({\n      method: 'openhuman.worktree_remove',\n      params: { path, force },\n    });\n    return res.removed;\n  },\n};\n","sourceCodeStart":66,"sourceCodeEnd":93,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/services/api/worktreeApi.ts#L66-L93","documentation":"A client-side precondition guard in worktreeApi.remove(): trims path and throws before the 'openhuman.worktree_remove' RPC when empty. Unlike status/diff this is a destructive action gated behind a confirm prompt; an empty path here means the remove flow was triggered without a target.","triggerScenarios":"A remove/confirm dialog whose submit handler reads an empty selectedPath (selection cleared while the dialog was open, or the dialog opened from a context menu with no row); a default parameter path of '' reaching the call.","commonSituations":"Confirm dialog outliving the selection (user deselects or the list refreshes while dialog open); keyboard-triggered remove with no focused row; race between list refetch resetting selection and the user clicking Remove.","solutions":["Close or disable the confirm dialog when the target path becomes empty","Capture the path at dialog-open time and re-validate on submit: if (!path.trim()) return;","Clear any pending remove intent when selection resets","Add a defensive early return in the submit handler rather than relying on the throw"],"exampleFix":"// before\nonConfirm={() => worktreeApi.remove(targetPath, force)}\n\n// after\nonConfirm={() => {\n  if (!targetPath.trim()) { setDialogOpen(false); return; }\n  worktreeApi.remove(targetPath, force);\n}}","handlingStrategy":"validation","validationCode":"// Re-validate at confirm time — selection may have changed since the dialog opened\nconst target = pendingRemovePath?.trim();\nif (!target) { closeDialog(); return; }\nawait worktreeApi.remove(target, force);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Capture the target path when opening the confirm dialog; never read live selection at submit","Close the dialog when the underlying selection/list resets","For destructive actions, always validate inputs at the last moment before the RPC"],"tags":["validation","precondition","worktree","destructive-action","ui"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}