fix: persist a context switch after its listeners, not before - #51
Merged
Conversation
Reported from drupal.z: a context was switched and remembered while the
composer install that belonged to it never ran. The order was the cause -
switchTo() persisted, then the listeners fired:
state.json currentContext: staging written
listener composer install failed
Every command after that ran as staging on a project only half prepared for it,
and nothing said so.
Switching a context means preparing the project for it, so the switch is written
down last. A listener that fails now leaves the previous context active and the
command exits non-zero.
context:switch staging, listener throws
before [OK] Switched from 'local' to 'staging' exit 0, staging persisted
after the work for this context failed exit 1, nothing persisted
Current context: local
No API change was needed. switchTo() still validates, switches and persists in
one call - the command validates first, dispatches, and switches afterwards, so
there is no half-applied state and no persist() anyone can forget to call.
Listeners do not need the persisted value: they read the context from the event,
and SwitchContextOnServices (priority 100) puts the resolver and the template
engine on the new one before any other listener runs. Verified before changing
the order, because it is what makes the change safe.
Also documents what was nowhere in docs/listeners.md: a listener returns nothing,
so throwing is how it reports that its work did not happen. Without that check a
failing command is visible in the output while the run still reports success -
which is the other half of this report, and stays true for events where there is
nothing to roll back.
refsz
force-pushed
the
fix/context-persists-after-listeners
branch
from
August 25, 2026 18:28
00557fa to
f159faf
Compare
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.
From the drupal.z report behind
PREFLIGHT.local.md: a context was switched and remembered while thecomposer installbelonging to it never ran. The reachability of the container was the trigger; the order was the cause.switchTo()persisted, and only then were the listeners dispatched:Every command after that ran as
stagingon a project only half prepared for it, and nothing said so. Measured against 0.2.4:Why last
Switching a context means preparing the project for it — regenerating templates, reinstalling dependencies — not just writing a name into a file. A switch whose preparation failed should not outlive the process.
What made it safe, and why I checked first
Listeners never needed the persisted value. They read the new context from the event, and
SwitchContextOnServices(priority 100) puts theVariableResolverand theTemplateEngineon it before any other listener runs. I verified that before touching the order, because it is the whole reason this is a reordering rather than a redesign.No API change.
switchTo()still validates, switches and persists in one call. The command validates first, dispatches, then switches — so there is no half-applied state, and nopersist()that a future caller can forget.The other half of the report
docs/listeners.mdnever said how a listener reports failure. It returns nothing, so throwing is the way — a command that fails is not a failure by itself, exactly as in a task. That is now documented with theisSuccessful()check spelled out, because without it the command's output is visible while the run still reports success.That remains true for events where there is nothing to roll back. Making a failed command inside a listener visible by itself is a separate change — the executor does not know who called it — and I would rather propose it on its own than fold it in here.
Tests
context:liststill reports the old oneWhile writing the first one I had the config wrong (
listeners:is not a key — listeners are discovered fromtasks.directories, as the docs say and drupal.z does). Worth noting that the top-level schema still accepts an unknown key like that silently; #40 closedvariables, not the root.Stacks conceptually with #50 but touches different files, so both can merge in any order.
🤖 Generated with Claude Code
https://claude.ai/code/session_018CTvnzcNYmFgm2HQcm821A