Skip to content

fix(stash): quiet-flag stash target resolution + working-log merge dedup + stash cycle regression tests#1722

Open
svarlamov wants to merge 1 commit into
mainfrom
fix/stash-mem-usage-bug-jul-cc
Open

fix(stash): quiet-flag stash target resolution + working-log merge dedup + stash cycle regression tests#1722
svarlamov wants to merge 1 commit into
mainfrom
fix/stash-mem-usage-bug-jul-cc

Conversation

@svarlamov

@svarlamov svarlamov commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #1719 (which fixed the stash checkpoint storage growth). While independently root-causing the same 18GB checkpoints.jsonl / 20GB+ daemon RSS explosion, end-to-end verification with a real daemon surfaced two additional bugs that #1719 did not cover, plus a broader regression-test suite for the stash/pop explosion.

1. git stash pop -q / apply -q never restored attribution (src/daemon/ref_cursor.rs)

enrich_stash treated the first argv token after the stash subcommand as the stash target: stash_args.get(1). For git stash pop -q that's -q, so stash-sha resolution failed and the attribution restore silently no-op'd — the stash entry was consumed by git but its attribution was dropped, and on pop the stashes_v2 entry was leaked (never cleaned up). Coding agents typically pass -q, so this is exactly the flow from the original bug report.

Fixed with stash_positional_arg, which skips flags when locating the positional target (and the branch-name/target positions for stash branch). Repro before fix: git stash push -q && git stash pop -q → attribution gone; without -q → restored.

2. merge_working_log_dirs duplicated identical checkpoints (src/git/repo_storage.rs)

When a working log merges into an existing one on HEAD moves (checkout/switch), checkpoints were concatenated with plain Vec::extend — a log merged forward and back accumulated duplicates, the same compounding shape as the stash bug at lower frequency. Now dedups by (kind, diff, timestamp, author) (diff is a content hash, so distinct checkpoints never collide).

3. Regression tests for the stash explosion (tests/integration/stash_attribution.rs)

TDD-written tests (each watched fail on the pre-#1719 code) covering the full bug surface, adapted to the compact stashes_v2 snapshot design:

  • test_stash_pop_cycles_do_not_grow_checkpoints — 5 push/pop cycles, live line count must not grow (was 32× pre-fix), no leaked stash entries
  • test_stash_push_clears_live_checkpoints_and_pop_restores
  • test_stash_apply_repeated_is_idempotent — incl. the conflict-tolerant re-restore after a failed second apply
  • test_stash_push_pathspec_keeps_unstashed_checkpoints_in_live_log — live-log complement of the stash-side pathspec filter
  • test_stash_pop_after_head_move_stays_bounded — shifted-pop path
  • test_legacy_stashes_dir_is_removed
  • test_stash_pop_quiet_flag_restores_attribution / test_stash_apply_quiet_flag_restores_attribution — the ref_cursor fix; fail without it
  • test_merge_working_log_dirs_dedups_identical_checkpoints (unit) — the repo_storage fix; fails without it

Verification

End-to-end with a real installed daemon (task dev): 10 files × 300 lines with AI checkpoints, then the reported repro — 20 manual stash push -q / pop -q cycles. checkpoints.jsonl byte-identical to baseline (7,931 bytes), daemon RSS flat (~3.5MB), zero leaked stash entries, legacy stashes/ dir removed, and git-ai blame shows full AI attribution after commit (broken before the ref_cursor fix).

Full stash suite (114 tests), ref_cursor/checkout/switch suites, task lint, task fmt all green locally.

🤖 Generated with Claude Code


Open in Devin Review

…op cycles

Stash push copied the working log into the stash dir but only cleared
INITIAL, never checkpoints.jsonl; pop then raw-appended the copy back.
Each push/pop cycle doubled the file (20 cycles ~= 10^6x), and since
append_checkpoint rewrites the whole file per checkpoint, a multi-GB
file turned into multi-GB daemon allocations (20GB+ RSS reports).

- clean_working_log_for_stash now truncates the live checkpoints.jsonl
  on full stash (without reading it, so push on an already-bloated log
  is an O(1) self-heal) and complement-filters it on pathspec'd stash,
  mirroring filter_stash_checkpoints_to_pathspecs on the stash copy.
- restore_stash_attributions dedups byte-identical lines on append, so
  repeated `stash apply` and conflict-tolerant re-restores are
  idempotent.
- Stash attribution dir is versioned (stashes -> stashes_v2) and the
  legacy dir is deleted on any stash op, so pre-fix multi-GB stash
  worklogs can never be re-appended or loaded into memory again.
- merge_working_log_dirs dedups checkpoints by (kind, diff, timestamp,
  author) so HEAD-move merges can't duplicate either.
- Fix `stash pop -q` / `apply -q` never restoring attribution:
  enrich_stash treated the first arg after the subcommand as the stash
  target, so boolean flags broke stash-sha resolution and the restore
  silently no-op'd.

All changes live in the async side-effect layer; nothing was added to
the ingestion path and no new git spawns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@Siddhant-K-code

Copy link
Copy Markdown
Collaborator

See: #1721

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants