Conversation
@zi-register-annex had no counterpart, so an annex following the Zsh Plugin
Standard unload contract had to remove its handler while its ZI_EXTS entry
survived. The before-load dispatch then calls a name that no longer exists.
That is not inert. A missing function returns 127, `127 & 1' is true, so Zi
folds the error into its return value and shifts its argument list. Reproduced
against a plug-in load after an annex removed its handler:
zi:176: command not found: probe_annex_handler
Warning: Bad new-ices returned from the annex: probe-annex
Error: No plugin or snippet ID given.
RC=2
The plug-in does not load at all; the shift consumed its id. Every later load in
the session is affected.
@zi-unregister-annex <name> <type> removes the matching ZI_EXTS and ZI_EXTS2
entries, matching on the recorded name and type rather than reconstructing the
key, since a hook registration appends a sequence number to it. Unregistering
something never registered is a no-op.
The ice-modifier lists in ZI_EXTS[ice-mods] and ZI_EXTS2[ice-mods] are not
unwound: registration appends to a joined string without recording which annex
contributed which entry, so removing one annex's share needs that association
recorded first. A leftover ice name is recognised but dispatches to nothing,
which is harmless; the dispatch entry removed here is the part that corrupts
loads. Noted in the function comment.
tests/annex-unregister.zsh registers a hook, unregisters it, removes the
handler, then loads two plug-ins and asserts both statuses, both bodies, and
that no ZI_EXTS entry survives. The second load is what proves the argument list
was not shifted. With the unregister made inert the test reports `loading after
unregister returned 2' and `Error: No plugin or snippet ID given.'
The wiki page ecosystem/annexes/0_overview.mdx documents the annex API and needs
a matching entry; that is a separate repository.
Closes #483
This was referenced Sep 3, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Adds
@zi-unregister-annex, the missing counterpart to@zi-register-annex.The defect
An annex following the Zsh Plugin Standard unload contract removes its handler. With no unregister call, its
ZI_EXTSentry survives and the before-load dispatch calls a name that no longer exists.That is not inert. A missing function returns 127,
127 & 1is true, so Zi folds the error into its return value and shifts its argument list. Reproduced by loading a plug-in after an annex removed its handler:The plug-in does not load at all: the shift consumed its id. Every later load in the session is affected. That is why
z-shell/z-a-meta-pluginscurrently leaves an inert stub behind instead of removing its handler.The API
Removes the matching
ZI_EXTSandZI_EXTS2entries. It matches on the recorded name and type rather than reconstructing the key, because ahook:registration appends a sequence number to its key. Unregistering something never registered is a no-op, not an error.One thing it deliberately does not do
The ice-modifier lists in
ZI_EXTS[ice-mods]andZI_EXTS2[ice-mods]are not unwound. Registration appends to a joined string without recording which annex contributed which entry, so removing one annex's share would require that association to be recorded at registration time first.A leftover ice name is recognised but dispatches to nothing, which is harmless. The dispatch entry removed here is the part that corrupts loads. This is stated in the function comment rather than left for someone to discover.
Test
tests/annex-unregister.zshregisters a hook, unregisters it, removes the handler, then loads two plug-ins and asserts both statuses, both bodies, and that noZI_EXTSentry survives. The second load is what proves the argument list was not shifted by the first.With the unregister made inert:
Registered in
zsh-n.yml, both path filters and a job. Full suite 22/22.Follow-up not in this PR
ecosystem/annexes/0_overview.mdxin the wiki documents the annex API and needs a matching entry. That is a separate repository, so it is not bundled here.Closes #483