stablyai/orca · critical

Path "${filePath}" resolves outside the worktree

Error message

Path "${filePath}" resolves outside the worktree

What it means

discardChanges resolves the target path against the worktree, then calls isWithinWorktree; if the resolved target equals the worktree root or escapes it (relative is '', '..', or starts with '..<sep>'), it throws before touching git. Without this guard, git restore / git clean could be pointed at files outside the worktree, so it is a security boundary. Note that relative '' (target is the worktree itself) is rejected — you cannot discard the worktree root.

Source

Thrown at src/main/git/status.ts:2138

  } finally {
    invalidateGitReadCaches()
  }
}

/**
 * Discard working tree changes for a file.
 */
export async function discardChanges(
  worktreePath: string,
  filePath: string,
  options: GitRuntimeOptions = {}
): Promise<void> {
  invalidateGitReadCaches()
  const resolvedWorktree = path.resolve(worktreePath)
  const resolvedTarget = path.resolve(worktreePath, filePath)
  try {
    if (!isWithinWorktree(path, resolvedWorktree, resolvedTarget)) {
      throw new Error(`Path "${filePath}" resolves outside the worktree`)
    }

    let tracked = false
    try {
      await gitExecFileAsync(
        ['ls-files', '--error-unmatch', '--', literalPathspec(filePath, options)],
        {
          ...gitOptionsForWorktree(worktreePath, options)
        }
      )
      tracked = true
    } catch {
      // File is not tracked by git
    }

    if (tracked) {
      await gitExecFileAsync(
        ['restore', '--worktree', '--source=HEAD', '--', literalPathspec(filePath, options)],

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Send file paths relative to the worktree, as git reports them, and ensure they are non-empty.
  2. Reject '', '.', and '..' at the renderer before issuing a discard IPC.
  3. If symlinks inside the worktree point outside, treat the discard as unsupported for those entries rather than letting resolve() escape.
  4. Use isWithinWorktree (exported) at the caller to pre-screen bulk paths.

Example fix

// before
discardChanges(worktreePath, rawPath)

// after: pre-screen at the caller
import { isWithinWorktree } from './status'
import path from 'node:path'
const resolvedTarget = path.resolve(worktreePath, rawPath)
if (!rawPath || !isWithinWorktree(path, path.resolve(worktreePath), resolvedTarget)) {
  throw new Error('Refuse to discard a path outside the worktree.')
}
discardChanges(worktreePath, rawPath)
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path'
import { isWithinWorktree } from './status'

function isSafeDiscardTarget(worktreePath: string, filePath: string): boolean {
  if (!filePath) return false
  const root = path.resolve(worktreePath)
  const target = path.resolve(root, filePath)
  return isWithinWorktree(path, root, target)
}

if (!isSafeDiscardTarget(worktreePath, filePath)) throw new Error('Refuse to discard a path outside the worktree.')

Type guard

function isDiscardOutsideWorktree(error: unknown): boolean {
  return error instanceof Error && /^Path ".+" resolves outside the worktree$/.test(error.message)
}

Try / catch

if (!isSafeDiscardTarget(worktreePath, filePath)) {
  showStatus('Cannot discard: path is outside the worktree.')
  return
}
try { await discardChanges(worktreePath, filePath, options) }
catch (error) { if (isDiscardOutsideWorktree(error)) { showStatus('Cannot discard: path is outside the worktree.'); return } throw error }

Prevention

When it happens

Trigger: Calling discardChanges(worktreePath, filePath) where filePath is '', '.', '..', an absolute path outside the worktree, or a relative path that climbs above the worktree root (e.g. '../../etc/passwd'). Also when filePath resolves to the worktree directory itself.

Common situations: A UI discard action that sent an empty or stale file path; a path that was pre-resolved to absolute by the renderer; adversarial IPC input; symlinks inside the worktree pointing outside (resolve() follows them); race where the worktree moved between path capture and the discard call.

Related errors


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