pnpm/pnpm · error · PnpmError
FILTER_CHANGED
FILTER_CHANGED
Error message
Filtering by changed packages failed. ${'stderr' in err ? err.stderr as string : ''} What it means
Changed-package filtering (pnpm11/workspace/projects-filter/src/getChangedProjects.ts:80) shells out to `git diff --name-only --end-of-options <commit> -- <workingDir>` to list files changed since a base ref. Any non-zero git exit is wrapped as FILTER_CHANGED with git's stderr appended, so the underlying git failure (usually a bad or missing revision) is visible.
Source
Thrown at pnpm11/workspace/projects-filter/src/getChangedProjects.ts:80
async function getChangedDirsSinceCommit (commit: string, workingDir: string, testPattern: string[], changedFilesIgnorePattern: string[]): Promise<ChangedDir[]> {
let diff!: string
try {
diff = (
await execa('git', [
'diff',
'--name-only',
// Keeps an option-like `<since>` (`--output=...`) from being
// parsed as a git option — git rejects it as a bad revision.
'--end-of-options',
commit,
'--',
workingDir,
], { cwd: workingDir })
).stdout as string
} catch (err: unknown) {
assert(util.types.isNativeError(err))
throw new PnpmError('FILTER_CHANGED', `Filtering by changed packages failed. ${'stderr' in err ? err.stderr as string : ''}`)
}
const changedDirs = new Map<string, ChangeType>()
if (!diff) {
return []
}
const allChangedFiles = diff.split('\n')
// The prefix and suffix '"' are appended to the Korean path
.map(line => line.replace(/^"/, '').replace(/"$/, ''))
const patterns = changedFilesIgnorePattern.filter(
(pattern) => pattern.length
)
const changedFiles = (patterns.length > 0)
? micromatch.default.not(allChangedFiles, patterns, {
dot: true,
})
: allChangedFilesView on GitHub (pinned to 6261b7f388)
Solutions
- Confirm the ref resolves locally: `git cat-file -t <since>`; fix typos / use origin/main.
- Deepen or unshallow the clone so the base commit exists: `git fetch --unshallow` or increase fetch-depth.
- Run the command inside the repository working tree (not an exported copy).
Example fix
# before: shallow clone missing the base ref $ pnpm --filter='...@origin/main^' build # ERR_PNPM_FILTER_CHANGED: bad revision # after $ git fetch --unshallow origin main # or: git fetch --depth=500 origin main $ pnpm --filter='...@origin/main^' build
Defensive patterns
Strategy: validation
Validate before calling
import { execSync } from 'node:child_process'
function refExists (since: string, cwd: string): boolean {
try {
execSync(`git cat-file -e ${JSON.stringify(since)}^{commit}`, { cwd, stdio: 'ignore' })
return true
} catch {
return false
}
}
if (!refExists(since, repoDir)) {
await fetchDeeply(repoDir) // e.g. git fetch --unshallow
} Try / catch
try {
await runFiltered({ since })
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'ERR_PNPM_FILTER_CHANGED' && /bad revision|unknown revision/.test(String(err.message))) {
await gitFetchDeeply() // fix the clone, then retry once
return runFiltered({ since })
}
throw err
} Prevention
- Fetch full or sufficiently deep history in CI before using @since filters.
- Validate the `<since>` ref resolves (`git cat-file -t`) before invoking filtered commands.
- Always run pnpm inside the git working tree, never on an exported archive.
When it happens
Trigger: Using `--filter ...@<since>` (or otherwise triggering changed-files filtering) with a `<since>` that git cannot resolve: typo'd ref, a base commit absent from a shallow clone, running outside a git repository, or a corrupted .git.
Common situations: CI shallow clones (fetch-depth too small) where the base commit was never fetched; typo'd since value like 'mainn'; pipelines running on exported tarballs without .git.
Related errors
- LOCKFILE_MISSING_DEPENDENCY
- GIT_UNKNOWN_BRANCH
- GIT_NOT_CORRECT_BRANCH
- readPackage hook did not return a package manifest object. H
- readPackage hook returned package manifest object's property
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/1b350d288288f7ac.
Report an issue: GitHub.