stablyai/orca · error · Error

Access denied: worktree does not belong to repository

Error message

Access denied: worktree does not belong to repository

What it means

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.

Solutions

  1. 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.
  2. If the worktree was removed on the remote, refresh/purge the renderer's cached worktree list and reselect a current one.
  3. Ensure the path is a POSIX-style absolute path when repo.connectionId is set; normalizeRemoteHostedReviewPath uses posix.normalize, not local path.resolve.
  4. Confirm the repo connectionId maps to the same remote host that owns the worktree.

Example fix

// before
await ipc.call('hostedReview:forBranch', { repoPath, worktreePath: staleCachedPath })

// after
const worktrees = await ipc.call('worktree:list', { repoPath })
const match = worktrees.find(w => normalizeRemoteHostedReviewPath(w.path) === normalizeRemoteHostedReviewPath(worktreePath))
if (!match) throw new Error('worktree no longer registered')
await ipc.call('hostedReview:forBranch', { repoPath, worktreePath: match.path })
Defensive patterns

Strategy: validation

Validate before calling

const worktrees = await ipc.call('worktree:list', { repoPath })
const normalized = normalizeRemoteHostedReviewPath(worktreePath)
if (!worktrees.some(w => normalizeRemoteHostedReviewPath(w.path) === normalized)) {
  throw new Error('worktree not registered to this repo')
}

Type guard

function isRegisteredRemoteWorktree(path: string, worktrees: { path: string }[]): boolean {
  const target = posix.normalize(path).replace(/\/+$/, '')
  return worktrees.some(w => posix.normalize(w.path).replace(/\/+$/, '') === target)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/3159b3bbd042ab61. Report an issue: GitHub.

Appendix: source

Thrown at src/main/ipc/hosted-review.ts:55

}

async function resolveHostedReviewWorktreePath(
  repo: Repo,
  store: Store,
  worktreePath?: string
): Promise<string> {
  if (!worktreePath) {
    return repo.path
  }
  if (repo.connectionId) {
    const remoteWorktreePath = normalizeRemoteHostedReviewPath(worktreePath)
    const repoWorktrees = await listRepoWorktrees(repo)
    if (
      !repoWorktrees.some(
        (worktree) => normalizeRemoteHostedReviewPath(worktree.path) === remoteWorktreePath
      )
    ) {
      throw new Error('Access denied: worktree does not belong to repository')
    }
    return remoteWorktreePath
  }
  const resolvedWorktreePath = await resolveRegisteredWorktreePath(worktreePath, store)
  const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)
  const repoWorktrees =
    Object.keys(localGitOptions).length > 0
      ? await listRepoWorktrees(repo, localGitOptions)
      : await listRepoWorktrees(repo)
  if (!repoWorktrees.some((worktree) => resolve(worktree.path) === resolvedWorktreePath)) {
    throw new Error('Access denied: worktree does not belong to repository')
  }
  return resolvedWorktreePath
}

function normalizeRemoteHostedReviewPath(remotePath: string): string {
  if (!remotePath || remotePath.includes('\0')) {
    throw new Error('Access denied: invalid worktree path')

View on GitHub (pinned to 1136503c6a)