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.

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)

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.