name: git-stash-triage description: Industrial protocol for classifying, disposing, and (when appropriate) promoting pre-existing Git stashes to atomic commits or personal-sandbox branches — covers hang-free inspection, content-based classification, apply-not-pop verification, and rule-driven disposition. category: Git & Repository Management
Git Stash Triage Skill (v1)
Skill ID:
git-stash-triageVersion: 1.0.0 Standard: Agent Skills (agentskills.io)
Composition Rationale
This skill is a composer: it does NOT re-implement the logic for obtaining a stash’s parent commit; instead, it consumes
the git-stash-parent-commit base skill to obtain the commit hash and subject
line that was HEAD when each stash was created.
git-stash-parent-commit— invoked for each stash reference discovered in Phase 0. The skill callsscripts/get-stash-parent.ps1 -StashRef <ref>to obtain the parent commit hash and subject line, which are then displayed in the verdict table to aid disposition decisions.
The composer's domain‑specific value‑add over using the base skill alone: it integrates the origin‑commit data into the stash‑triage workflow, allowing the user to see where a stash came from when deciding whether to drop, apply, or split it.
Bidirectional discoverability: the base skill lists this composer in its ## Composition by Higher-Level Skills table.
Description
You discover one or more pre-existing entries in git stash list — created in
a prior session, by another tool, or by yourself before context-switching.
You need a disciplined protocol to:
- Inspect stash contents without hanging the terminal (the pager trap).
- Classify each stash by content into one of four disposition buckets.
- Decide the correct disposition with the user (no auto-destruction).
- Execute the disposition safely — apply-not-pop for whole-stash
execution (§4b/§4c), or per-file granular triage (§4d) when the user
requests fine-grained control or
git stash applyfails. - Drop only after the disposition is materialized and verified.
This skill is the read-then-decide complement to
git-atomic-commit-construction
§9 (which covers stash-as-shelf during commit reconstruction). This skill
covers stashes you did NOT just create.
When to Apply
Apply this skill when:
git stash listreturns ≥ 1 entry whose origin is unclear or stale.- A workspace switch / repo audit / pre-rebase check surfaces stashes that must be disposed of (not silently carried forward).
- Restoring an old WIP and promoting it to a real commit / branch is on the table.
Do NOT apply when:
- You just created the stash 5 seconds ago as a temporary shelf during the
same atomic-commit flow — use the inline
git stash popstep ingit-atomic-commit-construction§9. - The stash is known to belong to a feature you are about to resume — just
git stash pop(no triage needed).
Prerequisites
| Requirement | Minimum |
|---|---|
| VCS | Git 2.x+ |
| Shell | PowerShell 5.1+ or POSIX shell |
| Disposition authority | User authorization required for every destructive step (drop) |
Operational Logic
Phase 0 — Discover
Stashes are local refs (refs/stash plus reflog entries). They never push
with branches, so they are at risk if the clone is lost.
git -C <repo-path> stash list
git -C <repo-path> show-ref | Select-String stash # confirms refs/stash
git -C <repo-path> reflog stash # full history including dropped-but-not-pruned
For each stash reference returned by git stash list, the skill invokes the [git-stash-parent-commit](../git-stash-
parent-commit/SKILL.md) base skill to obtain the commit hash and subject line that was HEAD when the stash was created.
This information is stored for later display in the verdict table.
If git stash list returns no output AND show-ref | Select-String stash
also returns no output, there are no stashes — exit the skill.
[!IMPORTANT] If a UI client (VS Code Source Control, IntelliJ, GitKraken) shows stashes that
git stash listdoes not, check for secondary worktrees (git worktree list) — each worktree has independent stash refs not visible from sibling worktrees.
Phase 1 — Inspect Without Hanging
git stash show -p and git stash show --stat invoke a pager by default.
In agent-driven terminals (no TTY, or a TTY that the agent cannot interact
with), this hangs the entire VS Code window until manually killed.
Hang-free inspection protocol — always use --no-pager AND dump to a
file:
$repo = '<repo-path>'
# --stat first (fast overview)
git -C $repo --no-pager stash show --stat 'stash@{N}' | Out-File "$repo\.stash_stat.txt" -Encoding utf8
# Full patch (may be large)
git -C $repo --no-pager stash show -p 'stash@{N}' | Out-File "$repo\.stash_patch.txt" -Encoding utf8
To inspect changes scoped to a single pathspec, use git diff between the
stash and its parent (since git stash show does not accept pathspec):
git -C $repo --no-pager diff 'stash@{N}^' 'stash@{N}' -- '*.launch' `
| Out-File "$repo\.stash_diff.txt" -Encoding utf8
Read the dump files via your editor's read_file tool — never paginate in
the terminal.
[!CAUTION] Use
.stash_*.txtfilenames so the dumps are easy to spot ingit statusand explicitly delete in Phase 5. NEVER commit these files. Add them to.git/info/excludeif you intend to inspect repeatedly.
Phase 2 — Classify
For each stash, classify its content into one of four buckets:
| Bucket | Content fingerprint | Default disposition |
|---|---|---|
| A — Obsolete/duplicate | Changes are already merged, already on disk, or superseded by newer commits | DROP (after user confirms) |
| B — Active feature WIP | Source-code changes belonging to a known feature branch / Jira ticket | APPLY to that feature branch + atomic commit |
| C — Personal sandbox | IDE artifacts, machine-specific configs, build outputs, runtime-location tweaks — not for team origin | APPLY to personal-sandbox branch (delegate to git-personal-sandbox-remote) |
| D — Unknown / mixed | Unclear provenance OR mixes buckets B and C | SPLIT — apply, hunk-stage by classification, multiple atomic commits |
Classification heuristics:
- Paths under
.idea/,.vscode/,.metadata/,.settings/, generated Antbuild.xml,javaCompiler*.args,*.iml,Thumbs.db, IDE workspace files → Bucket C. - Paths under
src/,lib/,test/, application source → Bucket B (correlate with active feature branch via Jira ID in branch name). - Both → Bucket D.
- Empty stash, or stash whose diff is now a no-op against current HEAD
(
git diff <stash> HEADis empty) → Bucket A.
Phase 3 — Decide (User Authorization Gate)
Present the classification to the user as a verdict table:
stash@{0} Bucket C <hash> <subject> 46 files +17,155 PDE build artifacts + 2 launch tweaks
stash@{1} Bucket A <hash> <subject> 3 files +12 Already-committed README changes
stash@{2} Bucket B <hash> <subject> 5 files +130 WIP on SWIT-12345 feature/foo
For each row, propose the default disposition and request the user's
explicit go / start / numbered choice. NEVER auto-execute drops.
[!WARNING]
git stash drop/git stash pop/git stash clearare destructive. Stashes are NOT in the reflog after being dropped (reflog entries are garbage-collected). Lost stash content is unrecoverable withoutgit fsck --lost-foundheroics and may not be found at all. Always require explicit user authorization per stash.
Phase 4 — Execute Disposition
Choose the execution path based on the disposition decided in Phase 3:
- §4a (Bucket A): Drop the stash (after supersession verification).
- §4b (Bucket B/C): Apply all, then commit.
- §4c (Bucket D): Apply all, hunk-stage, multiple commits.
- §4d (Selective File Restoration): Walk through each changed file
individually — analyze, present findings, get user decision per file.
Use when
git stash applyfailed, the user requests per-file granularity, or the stash contains mixed file types needing individual per-type treatment.
4a — Bucket A (Drop)
Stronger pre-drop verification (recommended for safety stashes): before invoking
stash drop, run thegit-ref-content-auditper-file blob-equality audit to prove every file the stash captures (including its untracked tree at<stash>^3) is byte-identical or knowingly-refined in the disposition target (usuallyHEAD). A✅ FULLY SUPERSEDEDverdict upgrades Bucket A from "applied content already in tree" to "every stashed blob proven equal at HEAD".python3 .agents/skills/git-ref-content-audit/scripts/audit-ref-content.py \ --repo $repo --stash N --ref-b HEAD --show-diffs
git -C $repo stash drop 'stash@{N}'
git -C $repo stash list # verify N decremented or list empty
4b — Bucket B or C (Apply → Commit → Drop)
ALWAYS use apply not pop. pop drops the stash atomically with the
apply — if the apply succeeds but the subsequent commit fails (conflicts,
hook rejection, mis-staged hunks), you have neither the stash nor the
commit. apply preserves the stash until you have verified the commit.
# 1. (Pre-flight) Make sure the working tree is clean
git -C $repo status --short
# 2. (Optional) Switch to or create the destination branch
git -C $repo checkout <feature-branch> # Bucket B
# OR
git -C $repo checkout -b personal/<purpose> # Bucket C — see git-personal-sandbox-remote skill
# 3. Apply (NOT pop)
git -C $repo stash apply 'stash@{N}'
# 4. Inspect the working tree against the planned classification
git -C $repo status --short
git -C $repo diff --stat
# 5. Stage and commit atomically per git-atomic-commit-construction skill
git -C $repo add <paths>
git -C $repo commit -F <message-file> # see SSOT mandate below
# 6. Verify the commit
git -C $repo log -1 --format='%H %s'
git -C $repo diff HEAD~1 HEAD --stat
# 7. ONLY after the commit is verified, drop the stash
git -C $repo stash drop 'stash@{N}'
[!IMPORTANT] Commit message authoring — use the BOM-free, variable-expansion-safe pattern when authoring the message via PowerShell:
$msg = @' chore(scope): subject line Body paragraph... Use ${var} forms — single-quoted here-string PREVENTS expansion. '@ $utf8NoBom = [Text.UTF8Encoding]::new($false) [IO.File]::WriteAllText("$repo\.git\COMMIT_EDITMSG_NEW", $msg, $utf8NoBom)NEVER use
Out-File -Encoding utf8(writes BOM, leaks into commit subject as∩╗┐glyphs). NEVER use double-quoted here-strings (@"..."@) — they expand$variableand${variable}references mid-message, corrupting sentences likelocation=${workspace_loc}/....
4c — Bucket D (Split — Apply → Hunk-Stage → Multiple Commits)
Same as 4b but instead of staging whole files, use interactive add to separate hunks per classification bucket:
git -C $repo stash apply 'stash@{N}'
git -C $repo add -p # hunk-by-hunk: stage only Bucket B hunks
git -C $repo commit -F <feature-msg>
git -C $repo add -p # second pass: stage only Bucket C hunks
git -C $repo commit -F <sandbox-msg>
git -C $repo status --short # MUST be clean
git -C $repo stash drop 'stash@{N}'
Each commit MUST follow
git-atomic-commit-construction.
4d — Selective File Restoration (Per-File Triage)
Use this subsection instead of §4a–§4c when:
git stash applyfailed (divergent editor — seegit-pre-execution-safety-stash§1g for the safety-stash path; this subsection covers ALL stashes).- The user explicitly requests per-file granularity.
- The stash contains mixed file types requiring individual treatment per file type (user config vs auto-generated IDE state vs binary cache).
Core principle — analysis-first, action-on-command: For each changed file, the agent analyzes (stash vs HEAD; or stash vs on-disk when the HEAD version is gitignored), presents findings, recommends an action, and waits for the user to decide. No action is pre-determined for any file type.
Step 1 — List changed files
git -C <repo> diff stash@{N} HEAD --name-status
This lists every file that differs between the stash and HEAD, prefixed with
A (Added), M (Modified), or D (Deleted). A files do not exist in HEAD
— they must be located in the stash's index tree (stash@{N}^2) or untracked
tree (stash@{N}^3).
Step 2 — Per-file analysis loop
For each file in the --name-status output:
Determine the reference versions:
- If file exists in HEAD (M): compare
git diff stash@{N} HEAD -- <file>. - If file exists only in stash (A): determine source tree:
- Check stash^2 (index):
git ls-tree stash@{N}^2 | grep <path> - Check stash^3 (untracked):
git ls-tree stash@{N}^3 | grep <path>
- Check stash^2 (index):
- If HEAD version is gitignored: compare stash version vs on-disk file
(
diff <(git show stash@{N}:<path>) <path>) — the HEAD commit does not track it, but the file lives in the working tree.
- If file exists in HEAD (M): compare
Classify the file type and apply the appropriate analysis pattern:
File type Analysis pattern Typical recommendation settings.json(VS Code user settings)Compare keys line-by-line: stash-only keys, HEAD-only keys, common-modified keys Merge stash-only keys into HEAD (user decides) extensions.json(VS Code auto-generated)Verify HEAD version is current; skip if auto-regenerated Skip — auto-generated IDE state state.vscdb(SQLite binary — VS Code state)Delegate to vscode-state-vscdb-merge--jsonfor key-level comparison. Present stash-only/HEAD-only/common-modified key countsMerge stash-only keys if stash has unique keys (user authorizes) claude/*.json,claude/.last-cleanup(Claude config)Compare content; check if stash has newer/updated values Restore from stash if newer claude/projects/*.jsonl(gitignored, on-disk)Compare on-disk hash vs stash hash ( git hash-object <file>vsgit rev-parse stash@{N}:<path>)No action needed if hashes match; user decides if they differ .gitignore,.gitattributesShow diff User decides Other text files Show diff User decides Other binary files (non-SQLite) Show only stat (size, hash) User decides Present findings:
- Diff output (or key-level comparison for state.vscdb)
- Source (stash@{N} tracked / stash@{N}^2 index / stash@{N}^3 untracked)
- File type category with implications
- Recommended action
Wait for user decision. Options:
- restore: overwrite working tree with stash version (
git checkout stash@{N} -- <file>for tracked,git show stash@{N}^3:<path> > <path>for untracked). - skip: keep HEAD/disk version, ignore stash version.
- merge: for structured files (settings.json, state.vscdb) — merge stash-only content into HEAD/disk version.
- defer: skip for now, handle later.
- restore: overwrite working tree with stash version (
Proceed to the next file only after the user decides.
Step 3 — "Not in HEAD" rule presentation
Files that exist only in the stash (A in --name-status, or present in
stash^2/stash^3 but absent from HEAD and the working tree) are presented
with a strong recommendation to restore — the user chose to snapshot them;
the snapshot should be honored unless explicitly skipped. The final decision
is always the user's.
Step 4 — Restoring Added files from stash trees
When the user chooses restore for an A file:
- If the file was staged at stash time (found in stash^2):
git checkout stash@{N} -- <path> - If the file was untracked at stash time (found in stash^3):
git show stash@{N}^3:<path> > <path>
The stash^2 (index) and stash^3 (untracked) trees are read-only — these commands never modify the stash entry.
Step 5 — Verification
After all files are processed:
git -C <repo> status --short
git -C <repo> diff --stat
Confirm the working tree matches the expected state. Present a summary to the user listing which files were restored/skipped/merged/deferred.
Phase 5 — Clean Up Inspection Artifacts
Remove-Item "$repo\.stash_stat.txt", "$repo\.stash_patch.txt", "$repo\.stash_diff.txt" `
-ErrorAction SilentlyContinue
git -C $repo status --short # MUST be clean
SSOT Compliance
This skill consumes — never duplicates — the following authoritative rules:
- Commit construction — every commit produced in Phase 4 MUST follow
git-atomic-commit-constructionfor atomicity, staging discipline, and message format. - Commit messages — Conventional Commits subject + body per the
project's commit-message rules (resolved via
git-commit-message-rewordwhen retrofitting). - Personal sandbox routing — Bucket C dispositions MUST delegate
branch/remote setup to
git-personal-sandbox-remoterather than inventing a parallel scheme. - Push authorization — when the disposition includes a push, the
global "agent MUST NEVER
git pushautomatically" rule fromgit-atomic-commit-constructionapplies — explicit userstartrequired. - VS Code state.vscdb analysis — when Phase 4d encounters
state.vscdbfiles, analysis MUST usevscode-state-vscdb-merge'sscripts/analyze-state-vscdb.pyfor key-level comparison, never a raw binary diff. The--mergeflag MUST NOT be invoked without explicit user authorization after the analysis report is presented.
Anti-Patterns
| Anti-pattern | Why it's wrong | Correct alternative |
|---|---|---|
git stash show -p stash@{0} in agent terminal without --no-pager |
Hangs VS Code (pager blocks on TTY) | Phase 1 dump-to-file pattern |
git stash pop followed by attempt to commit |
If commit fails after pop, stash is gone | apply + verify + drop (Phase 4b) |
| Auto-drop "obviously obsolete" stash without user confirmation | Stash content is unrecoverable after drop | Phase 3 user gate |
Out-File -Encoding utf8 for commit message |
Writes UTF-8 BOM → subject shows ∩╗┐ glyph |
[IO.File]::WriteAllText with UTF8Encoding($false) |
Double-quoted here-string (@"..."@) for commit message body |
PowerShell expands $var / ${var} mid-message |
Single-quoted (@'...'@) here-string |
git stash show -p stash@{0} -- '*.launch' |
stash show -p does NOT accept pathspec — fails with "Too many revisions" |
Use git diff 'stash@{N}^' 'stash@{N}' -- '*.launch' instead |
| Prescribing a fixed per-file action during selective restoration (e.g., "settings.json → always merge") without presenting analysis first | File-specific action without user review bypasses the per-file triage gate | Always analyze stash vs HEAD (or stash vs on-disk), present findings, recommend action, then let user decide — even for well-known file types |
Showing raw git diff for state.vscdb binary files during per-file triage |
Binary diff is noise; no meaningful key-level differences visible | Delegate to vscode-state-vscdb-merge script with --json for per-key comparison |
Traceability
- Initial design driven by a live-session episode where an unaudited stash
containing 46 PDE artifacts + 2 personal launch tweaks was discovered,
classified as Bucket C (Personal Sandbox), and promoted to
personal/sandboxon a freshly-created personal remote via thegit-personal-sandbox-remoteskill — surfaced the hang-prevention, apply-not-pop, BOM-free, expansion-safe, andstash show -ppathspec-limitation rules captured here.