Skip to content

feat(cache): define retention and pruning for watched install snapshots #17

Closed

Description

@showxu

Summary

Watched installs can accumulate a separate repository checkout for each selected commit. Removing both the watch and its installed skill leaves those snapshots behind. There is no explicit retention or pruning contract in the current cache implementation.

Code evidence at 5d4987b:

  • Sources/Core/RuntimeService.swift:802 materializes a commit-specific checkout when the source checkout differs from the selected reviewed/baseline commit; :808 uses .agent/cache/watch-installs.
  • Sources/Core/ProcessRunner.swift:106 names snapshots using identity and commit prefix; :120 clones a repository with a working tree and .git.
  • Sources/Core/WatchLedger.swift:370 removes the watch record only; Sources/Core/Installer.swift:249 handles installed files/lock entries, with no snapshot reclamation path.

Use Case

Long-lived watches can leave many snapshots as reviewed commits advance. Each snapshot contains a working tree, including files outside the selected skill, as well as Git metadata. Local Git clones may share object storage through hard links; this report does not assume every snapshot duplicates every object byte.

Reproduction / current-state verification

Executed at 5d4987b.

Run in Bash after cd "<checkout>" && swift build, replacing <checkout> with the absolute path of the checkout at this revision. Use a fresh reproduction directory. The commands isolate HOME, agent configuration, and XDG_*, and remove their fixture root on exit.

set -eu
checkout="<checkout>"
skill_bin="$checkout/.build/debug/skill"
repro_root="$checkout/.build/issue-repro-watch-cache"
mkdir -p "$repro_root/home" "$repro_root/tmp" "$repro_root/project"
trap 'rm -rf "$repro_root"' EXIT
isolated() {
  env -i PATH="$PATH" HOME="$repro_root/home" \
    CODEX_HOME="$repro_root/home/.codex" \
    CLAUDE_CONFIG_DIR="$repro_root/home/.claude" \
    XDG_CONFIG_HOME="$repro_root/home/.config" \
    XDG_STATE_HOME="$repro_root/home/.local/state" \
    XDG_CACHE_HOME="$repro_root/home/.cache" TMPDIR="$repro_root/tmp" \
    GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null \
    GIT_AUTHOR_NAME=Fixture [email protected] \
    GIT_COMMITTER_NAME=Fixture [email protected] \
    "$@"
}
skill() { isolated "$skill_bin" "$@"; }
write_skill() {
  mkdir -p "$1"
  printf -- '---\nname: %s\ndescription: Reproduction fixture\n---\n%s\n' "$2" "$3" > "$1/SKILL.md"
}
cd "$repro_root/project"

write_skill "$repro_root/source/skills/demo" demo A
isolated git init -q -b main "$repro_root/source"
isolated git -C "$repro_root/source" add .
isolated git -C "$repro_root/source" commit -qm A
skill add "$repro_root/source" --watch-only --skill demo --agent codex
write_skill "$repro_root/source/skills/demo" demo B
isolated git -C "$repro_root/source" add .
isolated git -C "$repro_root/source" commit -qm B
skill add "$repro_root/source" --skill demo --agent codex
skill review check "$repro_root/source" --watch --path skills/demo
skill review done "$repro_root/source" --watch --path skills/demo --note reviewed
write_skill "$repro_root/source/skills/demo" demo C
isolated git -C "$repro_root/source" add .
isolated git -C "$repro_root/source" commit -qm C
skill add "$repro_root/source" --skill demo --agent codex
skill remove "$repro_root/source" --watch
skill remove demo --agent codex
isolated python3 - <<'CODE'
from pathlib import Path
cache=Path('.agent/cache/watch-installs')
print('retained checkouts:', sum((p/'.git').exists() for p in cache.iterdir()))
print('retained skill bodies:', sorted((p/'skills/demo/SKILL.md').read_text().splitlines()[-1] for p in cache.iterdir()))
CODE

Observed output (relevant lines; temporary paths shortened to <temp>):

watched source path skills/demo
recorded done source
removed source
Removing demo
removed <temp>/project/.agents/skills/demo
retained checkouts: 2
retained skill bodies: ['A', 'B']

Environment: Apple Swift 6.4, macOS 27.0.1 (26A434), arm64; CLI 0.2.1; validation date 2026-10-17.

Proposed Behavior

The maintainer should choose a bounded retention/pruning policy and its trigger: automatic reclamation, an explicit cleanup command, or both. Preserve snapshots still required by current installations or review state.

Completion checks:

  • Exercise several reviewed commits and verify the chosen retention bound or explicit cleanup behavior.
  • Removing all consumers makes the snapshots eligible for reclamation.
  • Active installation source paths and review baselines remain usable after pruning.
  • Document the policy and validate it with swift test.

Tradeoffs

Reusing an existing identity/commit snapshot is useful and already happens. Pruning must account for stored source paths and offline restore/review needs; blindly deleting every old directory could break health checks. A cheaper snapshot representation is optional, not a prerequisite for defining ownership and retention.

Compatibility Notes

This is a cache lifecycle proposal, not a claim that existing snapshot retention violates a published limit. No retention limit or deletion timing is assumed.

Related: #7 covers restore semantics, #9 covers preview writes, and #10 covers watch scope. Retention should follow the accepted scope model without reopening those issues. No duplicate found.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions