You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(events): preserve dispatcher ownership during cleanup - #5055
Fix shared integration cleanup silently deleting a user-owned .specify/events.py.
Previously, cleanup deleted the dispatcher whenever no other installed integration
referenced it, even if the integration performing cleanup had never owned it.
This affected incoming event-capable integration setup as well as teardown.
Require ownership evidence from the integration's current or previous manifest
before deleting the last managed dispatcher. Preserve unowned, recovered, and
partially owned files even when forced, retain dispatchers referenced by another
installed integration, and preserve modified managed dispatchers with a warning
unless forced. Consult the prior manifest during empty-event upgrades so genuine
last-consumer cleanup still works with a fresh output manifest.
Thread force through event emission, teardown, and upgrades; document the event
helper/override contract. Add positive and negative ownership coverage using
existing integrations, including public install, switch, and upgrade commands.
This change contains no new integration adapter or CLI guidance changes.
Regression evidence: replaying the original cleanup function from the branch's
base commit in an isolated pytest process made all 10 unowned-dispatcher cases
fail; the corrected implementation passed those same 10 cases. The tests exercise
both incoming installation and outgoing teardown without requiring the none
integration.
Testing
Tested locally with uv run specify --help
Ran existing tests with uv sync && uv run pytest
Tested with a sample project (if applicable)
Validation on commit 6b1316548c22fbd78c7bba74f54e57de9375d4d5:
uv sync --extra test: passed; created this worktree's own virtual environment.
uv run specify --help: passed, including a fresh pre-submission check.
LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/events/test_events.py tests/specify_cli/integrations/test_command_switch.py tests/specify_cli/integrations/test_command_upgrade.py -q -k 'DispatcherCleanupOwnership or DispatcherManifestClaimDroppedOnRetain or incoming_copilot_preserves_unowned_dispatcher or disabling_events_honors_modified_dispatcher_force': 28 passed, 228 deselected in the fresh pre-submission check.
git diff --check: passed.
Sample project: using this worktree's CLI in fresh subprocesses, ran specify init <temporary-project> --integration generic --integration-options="--commands-dir .agent/commands" --non-interactive --ignore-agent-tools, wrote # User-owned dispatcher to .specify/events.py, then ran specify integration switch copilot from that project. Both commands exited 0 and the dispatcher retained its original bytes.
The literal uv sync && uv run pytest command and the full repository suite were
not run. The targeted suites above used the documented worktree-local Python
interpreter instead of bare uv run pytest.
AI Disclosure
I did not use AI assistance for this contribution
I did use AI assistance (fill in the disclosure below)
AI disclosure: Authored on behalf of @mnriem with GitHub Copilot using
GPT-6.1 Sol (gpt-6.1-sol) in autonomous/autopilot mode; reasoning effort was not
explicitly configured in the task. AI assistance covered investigation,
implementation, regression tests, documentation, validation, and this PR text.
Require a current or prior integration manifest claim before removing the shared dispatcher. Preserve unowned, recovered, partial, modified, and still-referenced copies; honor force only for managed last-consumer cleanup.
Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Warn and preserve dispatchers when prior ownership metadata cannot be read or validated, or when non-forced content hashing fails. Continue uninstalling other owned files and preserve rollback of generated files after a failed incoming setup.
Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cleanup now warns and preserves the dispatcher when the prior manifest cannot be read or validated, or when non-forced dispatcher hashing encounters an I/O error. Other owned files still reach uninstall cleanup. Public install/switch regressions verify that a subsequent setup failure rolls back the generated files rather than leaving them untracked. Forced cleanup of a known managed dispatcher still skips the content read.
The focused regression selector (prior_manifest or invalid_incoming_manifest or unreadable_dispatcher) produced 10 failures and 1 pass before the fix; all 11 pass afterward.
AI disclosure: Posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol) in autonomous/autopilot mode; reasoning effort was not explicitly configured in the task. AI assistance covered the fixes, tests, documentation, validation, and this summary. Review threads were left unresolved for the reviewer.
Handle stale hashes for shared generated dispatchers
src/specify_cli/events/__init__.py:1762
This integration's recorded hash can be stale without any user edit. If A emitted an older dispatcher and B later emits a newer template, only B's manifest receives the new hash. Removing B retains the file for A, but subsequently removing A without force classifies those generated bytes as modified. A's claim has already been dropped, leaving an untracked dispatcher that later cleanup treats as unowned. Recognize an exact match with the current generated template, including platform newlines, after the existing ownership/reference guards, or synchronize the claiming manifests when rewriting the shared file. Add a regression covering different generated revisions.
…anup
Bind optional event-cleanup keywords before invocation so legacy overrides remain supported without retrying implementation errors. Recognize exact current generated dispatcher bytes after ownership and reference checks when another integration has refreshed a shared file.
Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addressed the compatibility finding and the stale shared-hash issue in 8ccdade.
Teardown now checks the bound remove_events signature before passing force. Legacy two-argument overrides remain callable, compatible overrides still receive the flag, and a TypeError raised inside an override propagates without a second invocation. Public catalog-installed adapter tests cover normal and forced uninstall and outgoing switch after unloading/reloading the adapter.
After the existing ownership and other-consumer guards, cleanup also recognizes byte-exact current generated dispatcher content with LF or CRLF endings. This permits last-consumer cleanup when another integration regenerated the dispatcher without updating the older manifest's hash. Custom edits and unowned, recovered, or partial copies remain protected. Regression coverage exercises two generated revisions, both newline styles, teardown, and empty-event upgrades.
Before the fix, the new focused cases produced 10 failures and 16 passes; afterward all 26 pass. The related event, install/switch/uninstall/upgrade, base-class, and manifest suites pass all 521 tests (12 warnings).
Commands:
LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/integrations/test_base.py tests/specify_cli/events/test_events.py tests/specify_cli/integrations/test_installed_adapters.py -q -k 'legacy_remove_events or teardown_forwards_supported_force or teardown_does_not_retry_remove_events_type_error or shared_dispatcher_revision or generated_template_does_not_establish_ownership': 26 passed.
AI disclosure: Posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol) in autonomous/autopilot mode; reasoning effort was not explicitly configured in the task. AI assistance covered investigation, implementation, tests, documentation, validation, and this summary. Review threads remain unresolved for the reviewer.
Validated commit 8ccdade with the complete uninstall and installed-adapter suites included, without a test-name filter. This closes the validation gap identified in review 5605585723; no implementation changes were needed for this round.
Result: 1,196 passed, 12 warnings in 421.86 seconds. The warnings concern existing legacy Kilocode and Bob layouts. The run covers event cleanup, public install/switch/uninstall/upgrade, the entire installed-adapter suite (including legacy-hook lifecycle regressions), base teardown, and manifest behavior. git diff --check also passed; the worktree remains clean. The full repository suite was not run.
AI disclosure: Posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol) in autonomous mode; reasoning effort was not explicitly configured in the task. AI assistance in this round covered reading the review, running the complete related suites, and reporting validation. No code or documentation changes were made, and no review threads were resolved.
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
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.
Description
Fix shared integration cleanup silently deleting a user-owned
.specify/events.py.Previously, cleanup deleted the dispatcher whenever no other installed integration
referenced it, even if the integration performing cleanup had never owned it.
This affected incoming event-capable integration setup as well as teardown.
Require ownership evidence from the integration's current or previous manifest
before deleting the last managed dispatcher. Preserve unowned, recovered, and
partially owned files even when forced, retain dispatchers referenced by another
installed integration, and preserve modified managed dispatchers with a warning
unless forced. Consult the prior manifest during empty-event upgrades so genuine
last-consumer cleanup still works with a fresh output manifest.
Thread force through event emission, teardown, and upgrades; document the event
helper/override contract. Add positive and negative ownership coverage using
existing integrations, including public install, switch, and upgrade commands.
This change contains no new integration adapter or CLI guidance changes.
Regression evidence: replaying the original cleanup function from the branch's
base commit in an isolated pytest process made all 10 unowned-dispatcher cases
fail; the corrected implementation passed those same 10 cases. The tests exercise
both incoming installation and outgoing teardown without requiring the
noneintegration.
Testing
uv run specify --helpuv sync && uv run pytestValidation on commit
6b1316548c22fbd78c7bba74f54e57de9375d4d5:uv sync --extra test: passed; created this worktree's own virtual environment.uv run specify --help: passed, including a fresh pre-submission check.LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/events tests/specify_cli/integrations/test_command_switch.py tests/specify_cli/integrations/test_command_upgrade.py tests/integrations -q: 3,290 passed, 8 skipped, 22 warnings.LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests/specify_cli/events/test_events.py tests/specify_cli/integrations/test_command_switch.py tests/specify_cli/integrations/test_command_upgrade.py -q -k 'DispatcherCleanupOwnership or DispatcherManifestClaimDroppedOnRetain or incoming_copilot_preserves_unowned_dispatcher or disabling_events_honors_modified_dispatcher_force': 28 passed, 228 deselected in the fresh pre-submission check.git diff --check: passed.specify init <temporary-project> --integration generic --integration-options="--commands-dir .agent/commands" --non-interactive --ignore-agent-tools, wrote# User-owned dispatcherto.specify/events.py, then ranspecify integration switch copilotfrom that project. Both commands exited 0 and the dispatcher retained its original bytes.The literal
uv sync && uv run pytestcommand and the full repository suite werenot run. The targeted suites above used the documented worktree-local Python
interpreter instead of bare
uv run pytest.AI Disclosure
AI disclosure: Authored on behalf of @mnriem with GitHub Copilot using
GPT-6.1 Sol (
gpt-6.1-sol) in autonomous/autopilot mode; reasoning effort was notexplicitly configured in the task. AI assistance covered investigation,
implementation, regression tests, documentation, validation, and this PR text.