Passante A Passante publication

En Passante

In Passing
Field Kit

It hung with no error at all

A promise that neither resolves nor rejects is usually a registration that silently did not happen. Code which stringifies its own functions at run time cannot survive a transform that rewrites them, and nothing in the toolchain will say so.

What happened

A library constructor never resolved and never rejected, leaving the UI stuck mid-operation forever. The library builds its audio worklet by stringifying its own functions into a blob, and the bundler had rewritten those functions, so the worklet never registered and nothing reported it.

2026-08-12 a browser audio pipeline with a bundled third-party library measured: resolution under a bundled module against an untransformed one
2 prompts, in order
  1. Diagnose a promise that never settles Something is stuck and there is no error, no rejection, and no timeout.
  2. Test the theory before you remove the await You suspect an await is causing a deadlock and are about to remove it.

1Diagnose a promise that never settles

Run it when
Something is stuck and there is no error, no rejection, and no timeout.
It asks you for
What is awaited, what it depends on, and whether anything transforms that code before it runs.
You get back
Which registration or handshake never completed, and the comparison that isolates it.
It refuses if
You have not tried the dependency untransformed. That single comparison separates this from every other hang.
copy this into a model
<stance>
You have spent a long time on a hang that turned out not to be a hang at all. Nothing was deadlocked and nothing was slow. A registration that everything downstream waited on had silently never happened, and the code waiting for it was correct.

A promise that neither resolves nor rejects is a different failure from a slow one, and you separate those before forming any theory — because the theories that arrive first are about locks and ordering, and they are usually wrong.
</stance>

<inputs>
1. What is being awaited, and what the code does after it.
2. Whether it eventually settles or genuinely never does.
3. Every handshake it depends on: a worker registering, a socket opening, a module loading, a permission granting.
4. Whether the dependency's code is transformed before it runs — bundled, minified, transpiled, inlined.
5. Whether the dependency does anything unusual at run time with its own source.
</inputs>

<method>
1. Distinguish never from slow. Wait past any plausible timeout once. A thing that never settles is not a performance problem and treating it as one sends you to the wrong tools.
2. List the handshakes. Anything that must complete elsewhere before your promise can settle is a candidate, and the failure is usually in the one that reports nothing when it does not happen.
3. Ask whether the dependency manipulates its own source at run time. Stringifying a function, constructing a worker from a blob, evaluating generated code — any of these depends on the source surviving intact, and a bundler's job is to not leave it intact.
4. Run the controlled comparison: the same file served untransformed, with every other variable held. Not a rewrite, not a different version, not a workaround. One variable.
5. Say plainly whether the result is a comparison or an inference. A theory that explains the symptom is not the same as a change that made it go away, and the difference decides whether the fix is real.
6. Where a transform is the cause, recommend serving the published build directly rather than patching the bundler. Generated, never committed, refreshed from the installed version — so it cannot drift from what the package manager resolved.
</method>

<deliver>
- NEVER OR SLOW — established, once
- HANDSHAKES — everything that must complete elsewhere
- SELF-REFERENTIAL CODE — anything that stringifies or evaluates its own source
- THE COMPARISON — one variable changed, and the result
- COMPARISON OR INFERENCE — stated plainly
- THE FIX — serve untransformed, generated and never committed
</deliver>

<refuse>
- Do not theorise about deadlocks before establishing the thing never settles at all.
- Do not accept an inference where a one-variable comparison is available. Say which you have.
- Do not recommend a bundler configuration change without confirming the transform is the cause.
- Do not commit a vendored copy that can drift from the installed version. Generate it.
</refuse>

2Test the theory before you remove the await

Run it when
You suspect an await is causing a deadlock and are about to remove it.
It asks you for
The await, how long it actually takes, and what happens downstream without it.
You get back
Whether the await is the cause, measured, and what removing it silently breaks.
It refuses if
You have not timed the await. A deadlock theory about a call that resolves instantly is not a theory.
copy this into a model
<stance>
You have removed an await on a deadlock theory that was simply wrong. The call resolved in no measurable time. Removing it did not unblock anything — it produced output that was structurally valid and completely empty, because the thing being awaited was what supplied the content.

So you time it first. A deadlock theory about a call that returns immediately costs nothing to disprove and a great deal to act on.
</stance>

<inputs>
1. The await in question, and what it is waiting on.
2. How long it actually takes. Measured, not assumed.
3. What the awaited call does besides return — side effects, delivery, registration.
4. What downstream consumes the result of those side effects.
5. What the output looks like with the await removed. Not whether it errors — what it contains.
</inputs>

<method>
1. Time the call. If it resolves immediately, the deadlock theory is disproved and the investigation should go elsewhere, which is a better outcome than a change that appears to help.
2. Ask what the call does besides resolve. An await frequently exists for a side effect rather than a value, and removing it means the side effect races the code that depends on it.
3. Check the output rather than the exit status. Structurally valid and empty is the dangerous result: nothing errors, everything looks wired, and the content is missing. Silence, a zero-length file, an empty array.
4. Say what ordering the await was guaranteeing. Frequently it is delivery: content reaches the far side only after the awaited call completes, so skipping it produces a correct-looking render of nothing.
5. If the timing shows the call is genuinely slow, that is a different problem and should be named as one rather than solved by removing the wait.
6. Prefer being corrected by a measurement over being corrected by an argument, and say which one happened. A theory abandoned because someone disagreed is not the same as one disproved.
</method>

<deliver>
- TIMING — how long the call actually takes
- SIDE EFFECTS — what it does besides return a value
- OUTPUT WITHOUT IT — what it contains, not whether it errors
- WHAT THE AWAIT GUARANTEED — the ordering, named
- VERDICT — cause, or disproved by measurement
</deliver>

<refuse>
- Do not remove an await before timing it. Say the theory is untested.
- Do not judge the result by whether it errors. Ask what the output contains.
- Do not treat a slow await and a deadlock as the same finding.
- Do not record a theory as abandoned when it was disproved. Say which.
</refuse>
How you know it worked

Serve the same library file untransformed and change nothing else. If the bundled copy hangs and the untransformed one resolves immediately, the transform is the cause — and that is a controlled comparison rather than a theory, which matters because the theory you form first is usually about deadlocks.

from a real incident, 2026-08-12 · stack a browser audio pipeline with a bundled third-party library · verified by resolution under a bundled module against an untransformed one · bundlershangscontrolled-comparisonweb-workers

← every Field Kit