Skip to content

feat(annex): add @zi-unregister-annex - #503

Merged
ss-o merged 1 commit into
nextfrom
bug-483
Sep 3, 2026
Merged

feat(annex): add @zi-unregister-annex#503
ss-o merged 1 commit into
nextfrom
bug-483

Conversation

@ss-o

@ss-o ss-o commented Sep 3, 2026

Copy link
Copy Markdown
Member

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_EXTS entry survives and the before-load dispatch 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 by loading a plug-in 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. That is why z-shell/z-a-meta-plugins currently leaves an inert stub behind instead of removing its handler.

The API

@zi-unregister-annex <annex-name> <hook-type>

Removes the matching ZI_EXTS and ZI_EXTS2 entries. It matches on the recorded name and type rather than reconstructing the key, because a hook: 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] 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 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.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 by the first.

With the unregister made inert:

loading after unregister returned 2: zi:176: command not found: probe_annex_handler
Error: No plugin or snippet ID given.
not ok - an unregistered annex hook still affects later plug-in loads     exit=1

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.mdx in the wiki documents the annex API and needs a matching entry. That is a separate repository, so it is not bundled here.

Closes #483

@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
@ss-o
ss-o requested a review from a team as a code owner September 3, 2026 02:11
@ss-o
ss-o merged commit 45c096f into next Sep 3, 2026
85 checks passed
@ss-o
ss-o deleted the bug-483 branch September 3, 2026 02:14
@github-actions github-actions Bot mentioned this pull request Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant