{"record":{"id":"3159b3bbd042ab61","repo":"stablyai/orca","slug":"access-denied-worktree-does-not-belong-to-reposit","errorCode":null,"errorMessage":"Access denied: worktree does not belong to repository","messagePattern":"Access denied: worktree does not belong to repository","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/hosted-review.ts","lineNumber":55,"sourceCode":"}\n\nasync function resolveHostedReviewWorktreePath(\n  repo: Repo,\n  store: Store,\n  worktreePath?: string\n): Promise<string> {\n  if (!worktreePath) {\n    return repo.path\n  }\n  if (repo.connectionId) {\n    const remoteWorktreePath = normalizeRemoteHostedReviewPath(worktreePath)\n    const repoWorktrees = await listRepoWorktrees(repo)\n    if (\n      !repoWorktrees.some(\n        (worktree) => normalizeRemoteHostedReviewPath(worktree.path) === remoteWorktreePath\n      )\n    ) {\n      throw new Error('Access denied: worktree does not belong to repository')\n    }\n    return remoteWorktreePath\n  }\n  const resolvedWorktreePath = await resolveRegisteredWorktreePath(worktreePath, store)\n  const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)\n  const repoWorktrees =\n    Object.keys(localGitOptions).length > 0\n      ? await listRepoWorktrees(repo, localGitOptions)\n      : await listRepoWorktrees(repo)\n  if (!repoWorktrees.some((worktree) => resolve(worktree.path) === resolvedWorktreePath)) {\n    throw new Error('Access denied: worktree does not belong to repository')\n  }\n  return resolvedWorktreePath\n}\n\nfunction normalizeRemoteHostedReviewPath(remotePath: string): string {\n  if (!remotePath || remotePath.includes('\\0')) {\n    throw new Error('Access denied: invalid worktree path')","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/hosted-review.ts#L37-L73","documentation":"Thrown by the hosted-review IPC handler when a remote (SSH/connectionId) repository receives a worktreePath that does not match any worktree returned by listRepoWorktrees after posix normalization. It is an authorization guard: the caller is asking to operate on a worktree the repository does not actually own. The path comparison uses normalizeRemoteHostedReviewPath on both sides so trailing slashes and relative segments are canonicalized before matching.","triggerScenarios":"Calling a hosted-review IPC method with a repo that has connectionId set, passing a worktreePath that is not among the paths returned by listRepoWorktrees(repo). Mismatches include wrong absolute path, a worktree registered under a different repo, a stale path from a removed worktree, or a path that resolves differently on the remote POSIX host.","commonSituations":"The renderer caches an old worktree path after the worktree was deleted or moved on the remote host. A user switches repos in the UI but the previously selected worktree path is still sent. Cross-platform path separators (backslash from a Windows client sent to a POSIX SSH host) cause the normalized forms to disagree. Typos or truncated paths in serialized state.","solutions":["Verify the worktreePath you pass is one of the exact paths returned by the worktree-listing IPC for that repo before invoking the hosted-review call.","If the worktree was removed on the remote, refresh/purge the renderer's cached worktree list and reselect a current one.","Ensure the path is a POSIX-style absolute path when repo.connectionId is set; normalizeRemoteHostedReviewPath uses posix.normalize, not local path.resolve.","Confirm the repo connectionId maps to the same remote host that owns the worktree."],"exampleFix":"// before\nawait ipc.call('hostedReview:forBranch', { repoPath, worktreePath: staleCachedPath })\n\n// after\nconst worktrees = await ipc.call('worktree:list', { repoPath })\nconst match = worktrees.find(w => normalizeRemoteHostedReviewPath(w.path) === normalizeRemoteHostedReviewPath(worktreePath))\nif (!match) throw new Error('worktree no longer registered')\nawait ipc.call('hostedReview:forBranch', { repoPath, worktreePath: match.path })","handlingStrategy":"validation","validationCode":"const worktrees = await ipc.call('worktree:list', { repoPath })\nconst normalized = normalizeRemoteHostedReviewPath(worktreePath)\nif (!worktrees.some(w => normalizeRemoteHostedReviewPath(w.path) === normalized)) {\n  throw new Error('worktree not registered to this repo')\n}","typeGuard":"function isRegisteredRemoteWorktree(path: string, worktrees: { path: string }[]): boolean {\n  const target = posix.normalize(path).replace(/\\/+$/, '')\n  return worktrees.some(w => posix.normalize(w.path).replace(/\\/+$/, '') === target)\n}","tryCatchPattern":null,"preventionTips":["Always source worktreePath from a fresh worktree list for the same repo, never from cached/stale state.","When repo.connectionId is set, use POSIX absolute paths matching the remote host.","Invalidate cached worktree paths when the worktree list changes."],"tags":["security","authorization","worktree","remote","ipc"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}