fix(shared-infra): remove stale managed scripts the core no longer ships (#3076)#3098
Conversation
…ips (github#3076) install_shared_infra never removed shared scripts a prior (pre-refactor) install recorded but the current core no longer ships — e.g. the legacy scripts/<variant>/update-agent-context.sh, superseded by the bundled agent-context extension. On a legacy project the orphan lingers and crashes when it sources a refreshed common.sh (HAS_GIT unbound under set -u). Apply the stale-removal that integration_upgrade already performs to install_shared_infra: manifest-tracked scripts the current bundle no longer produces are removed, but only managed copies (hash matches the manifest); user-customized files, symlinks, and recovered entries are preserved. Guarded so a missing/empty source can't trigger mass deletion, and the safe-destination check prevents unlinking through a symlinked ancestor. Add IntegrationManifest.remove(); drop the stale update-agent-context.sh reference in CONTRIBUTING.md. AI assistance: implemented with Claude Code (Anthropic); reviewed and validated locally (ruff clean, full suite 4176 passed, manual CLI repro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates Spec Kit’s shared infrastructure installer to proactively remove manifest-tracked legacy scripts that the core no longer ships (addressing #3076), preventing orphaned scripts from breaking after refactors (e.g., old update-agent-context.sh sourcing a newer common.sh).
Changes:
- Track which shared script/template paths the current bundle produces and delete obsolete managed scripts from prior installs during
install_shared_infra. - Add
IntegrationManifest.remove()to keep manifests consistent when callers delete tracked files. - Add regression tests for stale-script deletion vs. preserving user-modified stale scripts; update
CONTRIBUTING.mdto remove the stale script reference.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/specify_cli/shared_infra.py |
Implements stale managed-script cleanup during shared infra install, with safety checks and manifest updates. |
src/specify_cli/integrations/manifest.py |
Adds IntegrationManifest.remove() helper to drop tracked entries without reaching into internals. |
tests/integrations/test_cli.py |
Adds unit tests covering stale managed-script removal and preservation when modified. |
CONTRIBUTING.md |
Removes mention of legacy update-agent-context.sh as a transitive dependency of common.sh. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…phan manifest) - Set scripts_scanned only after a real source file is seen, so an empty variant source can't trigger mass deletion of tracked scripts. - Prune a stale manifest entry even when its file is already gone from disk, keeping the manifest consistent (previously left tracked forever). - Add a test for each edge case. Addresses the Copilot review comments on github#3098. AI assistance: Claude Code (Anthropic), reviewed/validated locally (ruff clean, full suite 4178 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review — both points addressed in ffca99c:
Full suite green (4178 passed), ruff clean. |
- Skip absolute / '..' manifest keys before any filesystem access in stale-cleanup, so a corrupted/hand-edited manifest can't make it touch paths outside the project root (mirrors IntegrationManifest.check_modified / uninstall). - Clarify the scripts_scanned comment: the safety hinge is that flag, not seen_rels (which also holds template paths). - Add a containment test: a traversal manifest key is skipped, its target untouched. Addresses the second round of Copilot review on github#3098. AI assistance: Claude Code (Anthropic); validated locally (ruff clean, full suite 4179 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — second round addressed in the latest push:
Full suite green (4179 passed), ruff clean. |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
…s (review) IntegrationManifest.remove() now applies the same lexical validation and normalization as record_existing() / is_recovered(): absolute paths and '..' segments are rejected (return False) instead of being used verbatim as a key. Keeps the manifest API consistent. Adds tests (valid drop + no-op, absolute rejected, traversal rejected). Addresses the third round of Copilot review on github#3098. AI assistance: Claude Code (Anthropic); validated locally (ruff clean, full suite 4182 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @mnriem — all of Copilot's feedback is now addressed across the three rounds:
Each fix has a regression test. Full suite green (4182 passed), ruff clean. Ready for re-review whenever you have a moment — thanks for the careful look. |
|
Please address Copilot feedback |
…ust lexically (review) The stale-script cleanup guarded manifest keys with a lexical check only (is_absolute() / ".." segments). On Windows a drive-relative key such as "C:tmp\\file" is not is_absolute(), yet joining it onto the project path discards the root — so cleanup could stat/unlink outside the project before _ensure_safe_shared_destination raised, and a corrupted manifest key turned into an install-time hard failure (ValueError) instead of being skipped. Reuse the canonical containment helper (_validate_rel_path, the same one IntegrationManifest.is_recovered / remove use): after the fast lexical reject, resolve the join and confirm it stays within the project root; a key that still escapes is skipped, never unlinked, never fatal. Adds a regression test that forces _validate_rel_path to reject a managed key (portably simulating the Windows drive-relative escape) and asserts the install skips it without failing and still installs the real scripts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thank you! |
Description
Fixes #3076.
install_shared_infranever removed shared scripts that a prior (pre-refactor) install recorded but the current core no longer ships — e.g. the legacyscripts/<variant>/update-agent-context.sh, superseded by the bundledagent-contextextension. On a legacy project the orphan lingers, and because a refreshedcommon.shno longer exportsHAS_GIT, running the old script crashes (HAS_GIT: unbound variableunderset -u). As discussed in the issue, the right fix is stale-file cleanup on (re)install, not re-introducingHAS_GIT/branch handling into a deleted script.This applies the same stale-removal that
integration_upgradealready performs (_migrate_commands.py) toinstall_shared_infra: after the bundle is processed, any manifest-tracked script under.specify/scripts/<variant>/that the current bundle no longer produces is removed. Safety:_is_managed). A user-customized file (hash diverges), a symlink, or arecoveredentry is preserved.scripts_scanned— if the script source is missing/empty (ordest_variantis symlinked and skipped), the cleanup does not run, so it can never trigger mass deletion.unlink, so a symlinked ancestor can't let a delete escape the project root.Also drops the stale
update-agent-context.shreference inCONTRIBUTING.md(it is no longer sourced bycommon.sh), and adds a smallIntegrationManifest.remove()so the manifest stays consistent without mutating its internals from outside.Scoped to the script variant the bug is about; templates are untouched. PowerShell legacy orphans are covered by the same code path (it iterates the active variant).
Testing
uv sync --extra test && .venv/bin/python -m pytest tests -q→ 4176 passed, 3 skipped (ruff clean)tests/integrations/test_cli.py): stale managed script removed + manifest stops tracking it; user-modified stale preserved (hash-safe)Manual test results
Agent: Claude Code · OS/Shell: Linux / bash
specify init --here --force --integration claude --script sh(with an injected manifest-tracked legacyupdate-agent-context.sh)⚠ Removed 1 obsolete shared script(s): .specify/scripts/bash/update-agent-context.sh; orphan gone from disk and manifest;common.shand the other shipped scripts preservedAI Disclosure
I used Claude Code (Anthropic) to explore the codebase, implement the fix following the existing
integration_upgradestale-cleanup pattern, and write the tests. All changes were reviewed and validated locally by me before submission.Note: I left the
CHANGELOG.mdentry out — entries carry the merged PR number and the<!-- insert new changelog below this comment -->marker suggests your release flow manages insertion. Happy to add one if you'd prefer it in the PR.