Skip to content

Cap the lifecycle of refs/attempt-preserve-dirty/* worktree-snapshot refs (follow-up to #32) #49

Description

@dracic

Background

#29 introduced two families of local recovery refs written before an auto-rollback's git reset --hard:

  1. refs/heads/attempt-preserve/<run_id>-<sha> branches parking committed work (preserve_commits), and
  2. refs/attempt-preserve-dirty/<run_id>-<baseline>-<attempt> refs parking the uncommitted working-tree snapshot (snapshot_worktree via _preserve_attempt_worktree).

#32 added bounded retention for family 1 (ScmPolicy.preserve_keep, pruned at run start) and explicitly fenced family 2 out of its scope.

Problem

Nothing ever removes the refs/attempt-preserve-dirty/* refs. Every dirty auto-rollback attempt creates one (keyed by attempt number, so retries each add a new ref), and they accumulate unbounded exactly like the branches did.

Scope / severity: low. Same as #32: these refs are local-only (never pushed) and, living outside refs/heads/, they don't even clutter git branch output — so the nuisance is smaller. But they still add ref-walking overhead forever and hold snapshot commits alive against git gc indefinitely.

Proposed resolution

Extend the run-start bounded retention to this family:

  • Reuse the existing scm.preserve_keep budget (simplest — one knob governs both recovery-ref families), or add a sibling field if independent tuning is wanted.
  • These are not branches, so delete via git update-ref -d (or batched git update-ref --stdin), not git branch -D.
  • for-each-ref --sort=-committerdate refs/attempt-preserve-dirty/ works unchanged: each ref points at the snapshot commit created at preservation time, so committer date is the park time here.
  • Keep the Cap the lifecycle of attempt-preserve/* recovery refs (follow-up to #29) #32 hardening: per-ref best-effort deletes (one stuck ref must not wedge the tail), full-refname handling, prune failures journalled and never blocking run start.

Acceptance criteria

  • refs/attempt-preserve-dirty/* refs are pruned to a bounded count at run start, keeping the most recently created.
  • Retention is configurable; 0/disabled keeps every ref (consistent with preserve_keep semantics).
  • Pruning only ever touches refs/attempt-preserve-dirty/* — never refs/heads/* (the Cap the lifecycle of attempt-preserve/* recovery refs (follow-up to #29) #32 prune already owns refs/heads/attempt-preserve/*).
  • Tests cover: keep-N boundary, ordering, disabled = no-op, a stuck ref not wedging the rest, and branches being untouched.

Notes

Non-blocking follow-up to #32; surfaced during its review as the symmetric gap for the second ref family.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions