fix: auto-stash creates empty stash objects from untracked noise (#19)#21
Merged
stackbilt-admin merged 2 commits intomainfrom Apr 9, 2026
Merged
fix: auto-stash creates empty stash objects from untracked noise (#19)#21stackbilt-admin merged 2 commits intomainfrom
stackbilt-admin merged 2 commits intomainfrom
Conversation
The dirty-state preflight used:
if [[ -n "$(git status --porcelain)" ]]; then
git stash push --include-untracked ...
fi
Two problems compounded:
1. `status --porcelain` includes untracked files. Autonomous runners
often operate in repos with untracked noise (charter telemetry at
`.charter/telemetry/events.ndjson`, build artifacts, IDE lockfiles).
The condition treated ANY of that as grounds to stash.
2. `git stash push --include-untracked` with only noise files produced
empty-diff stash objects. There was no post-push verification, so
empty stashes accumulated silently across every task run.
Downstream evidence from a production fork: 10 auto-stashes piled up,
9 with empty diffs and the 10th containing only 36 lines of telemetry.
None contained real work.
Fix:
- Check for real tracked changes via `git diff --quiet` and
`git diff --cached --quiet` — these only detect staged/unstaged
modifications to tracked files, skipping untracked entirely.
- Dropped `--include-untracked` from the stash push. If untracked
files need to survive, the worktree cleanup handles them separately.
- Added an empty-stash guard: after push, verify the stash tree
differs from its parent. If not, drop the stash immediately.
Applied to both taskrunner.sh and plugin/taskrunner.sh.
Verified via synthetic 5-scenario test in the AEGIS fork:
clean tree → skipped ✓
untracked noise only → skipped ✓ (previously: empty stash)
tracked file modified → stashed ✓
stash pop recovery → works ✓
staged changes only → stashed ✓
Closes #19.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks
Updates the Branch Isolation section to accurately reflect the new stash logic: - Only stashes on real tracked changes (staged or unstaged) - Leaves untracked files alone (no more --include-untracked) - Verifies each stash captured content; drops empty stashes immediately The old description implied "any uncommitted work" got stashed, which was both inaccurate (the bug stashed untracked noise as empty objects) and misleading (readers couldn't tell why `git stash list` was piling up). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
stackbilt-admin
pushed a commit
to Stackbilt-dev/charter
that referenced
this pull request
Apr 9, 2026
Both packages are now powering downstream integrations in cc-taskrunner (Stackbilt-dev/cc-taskrunner#21, #22). Added "Downstream integrations" sections to both READMEs pointing at the taskrunner as a real-world example of wiring these primitives into a governance workflow. blast: - Documents cc-taskrunner 1.5.0's 4-level severity ladder with gate behavior table - Explains the auto_safe downgrade on critical severity - Points at compute_blast_radius() in taskrunner.sh surface: - Documents cc-taskrunner 1.4.0's mission brief fingerprint injection - Mentions the 80-line cap and graceful no-op behavior - Points at build_fingerprint() in taskrunner.sh Both sections help readers understand how these zero-dep analysis packages compose with real autonomous-agent workflows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
git diff --quietandgit diff --cached --quiet.--include-untrackedfrom the stash push — untracked files are left alone (the worktree cleanup path handles them separately).taskrunner.shandplugin/taskrunner.sh.Root cause
status --porcelainincludes untracked files. Running in a repo that has charter CLI writing telemetry to.charter/telemetry/events.ndjson(or any equivalent untracked noise) triggers a stash on every task run. Combined with--include-untracked, the stash object is created with a tree that ends up matching HEAD — empty diff, but still a real stash entry.Without a cleanup path, stashes pile up forever.
Real-world evidence
From a production fork after routine operation:
The fix
Test plan
Verified with a 5-scenario synthetic test in a throwaway repo:
All scenarios pass. Bash syntax verified via
bash -non both files.Related
.gitignorecleanup for**/.charter/telemetry/(which is what exposed the bug —.charter/telemetry/at the root.gitignoredoesn't match nested paths in monorepos).🤖 Generated with Claude Code