Track and upstream your local Yarn patches.
Monorepos accumulate yarn patch overrides, each one a diff a maintainer probably wants to see. patchlift keeps a sidecar JSON next to every patch in .yarn/patches/ recording its upstream state (filed? merged? rejected? never going upstream?), and gives you the commands to act on them.
pnpm add -D patchlift
# or one-shot:
pnpm dlx patchlift inspect# See all patches in the current project with their upstream status
patchlift inspect
# Inspect one patch — path or bare filename both work
patchlift inspect lodash-npm-4.17.21-abc123.patch
# File a GitHub issue: opens a pre-filled draft in your browser
patchlift issue lodash-npm-4.17.21-abc123.patch
# After you submit, record the URL (promotes the status to "proposed")
patchlift update lodash-npm-4.17.21-abc123.patch \
--issue https://github.com/lodash/lodash/issues/1234
# Mark a patch you've decided not to upstream
patchlift update my-hack.patch --status localonly --notes "project-specific tweak"Bare filenames (without .yarn/patches/ prefix) resolve automatically, so you rarely need full paths.
Tabular view of all patches in .yarn/patches/, or just the one you named. Shows package, version, status, and linked issue.
Handles patch chains (when yarn patch is applied on top of an existing patch) by resolving the true package name via the root package.json's resolutions, and renders the layers as an indented tree:
@endo-pass-style-npm-1.6.3-139d4e4c47.patch @endo/pass-style 1.6.3 untracked -
↳ @endo-pass-style-patch-fd208907c7.patch @endo/pass-style 1.6.3 untracked -
↳ @endo-pass-style-patch-613c0f4a7a.patch @endo/pass-style 1.6.3 untracked -
Also flags drift: when a patch file changes after its sidecar was written, the recorded hash no longer matches and a warning is printed beneath the row.
Options: --json (machine-readable), --verbose (dump each sidecar).
Generates a GitHub issue draft modeled after patch-package's upstream template — a friendly greeting, the diff, and a fill-in-the-blank block for the reporter's context. Opens the GitHub issue form in your browser by default.
Options: --title <s>, --summary <s>, --print (stdout), --no-web (skip browser), --issue <url> (record a manually-filed URL, usually with --no-web), --dry-run (don't write the sidecar).
Carry sidecar metadata from one patch to another — useful after a dependency version bump regenerates the patch. Preserves upstream URLs, status, notes, and createdAt; writes a fresh sidecar for <newPatch> with the new package version and patch hash. You're responsible for deleting the old patch afterward — git's rename detection should pair the sidecar add/delete as a move in the resulting commit.
Errors if <newPatch> already has a sidecar (delete it first if you really mean to overwrite).
Mutates the sidecar directly.
| Flag | Effect |
|---|---|
--issue <url> |
Record the issue URL. Implicitly promotes untracked → proposed. |
--pr <url> |
Record the PR URL. |
--status <value> |
Set status (validated against the list below). |
--notes <string> |
Freeform note. |
--clear |
Reset the sidecar to the initial untracked state. |
untracked ← default; no upstream action taken
│
│ file an issue
▼
proposed ← issue URL recorded
│
┌─────────┴─────────┐
▼ ▼
merged rejected
Two terminal statuses sit outside the upstream flow:
localonly— you've deliberately decided not to file upstream (project-specific hack, internal fork of your own dep, etc.). Distinct fromrejected, which implies "we tried, they declined."obsolete— the patch is no longer needed locally (upstream shipped a fix, you dropped the dependency, etc.).
Each patch foo.patch gets a companion file .patchlift/foo.yml:
schemaVersion: 1
patchHash: "sha256:…"
package:
name: lodash
version: "4.17.21"
upstream:
repo: lodash/lodash
issue: https://github.com/lodash/lodash/issues/1234
pr: null
status: proposed
notes: null
createdAt: 2026-04-15T16:00:00.000Z
updatedAt: 2026-04-15T16:30:00.000ZCommit these alongside the patch files — they're project state, not local scratch.
MIT — see LICENSE.