Skip to content

UN-4136 [FIX] Stop the file_processing worker booting the executor stack on each child's first task - #2293

Open
ritwik-g wants to merge 12 commits into
mainfrom
perf/executor-registry-import
Open

ritwik-g wants to merge 12 commits into
mainfrom
perf/executor-registry-import

Conversation

@ritwik-g

Copy link
Copy Markdown
Contributor

What

Each pg_queue_consumer child paid ~9s of one-time lazy initialisation on its first task. The supervisor preforks 20 children per pod, so that is ~170 CPU-seconds per pod, spent during serving rather than at startup.

This stops the file_processing worker from importing the executor stack at all.

Why it costs so much

workers/file_processing/structure_tool_task.py has three function-local imports from the executor package — two for string constants, one for a shim:

line 207  from executor.executors.constants import PromptServiceConstants as PSKeys
line 238  same
line 321  from executor.executor_tool_shim import ExecutorToolShim

None of them needs the executor registry. But every import under the executor package booted the whole thing:

executor/__init__.py:8      from .worker import app as celery_app
executor/worker.py:84       import executor.executors    # LegacyExecutor + entry-point discovery

So line 321 — which fires first, in step 1 of _execute_structure_tool_impl — dragged in LegacyExecutor and every adapter behind it, to construct a StreamMixin wrapper. Measured on origin/main, a py3.12 venv, with the file_processing worker already imported as the baseline:

from executor.executor_tool_shim import ExecutorToolShim   9.98s (cold)
  pulled in executor.worker                     True
  pulled in executor.executors.legacy_executor  True

Because the imports are function-local, the cost is deferred to each child's first task — which is why CAST AI's startup: {periodSeconds: 120} window cannot exclude it. That window is anchored to pod start; on staging, pod ...-cg9nt started ~07:45 IST and its children initialised at 11:08, 3.4 hours later.

Consequences this fixes

  1. Fleet CPU spikes to 16-17 cores at load onset, driving a spurious HPA scale-up to maxReplicas 8 that then unwinds.
  2. It is what CAST AI's WOOP p99 measures. The recommendation lands on the warm-up spike — 792m against a chart request of 200m. That inflated denominator puts the HPA signal's ceiling (29-31% at full slot occupancy) below its own 70% target, so the HPA can never scale up and collapses to minReplicas even with a 90-message backlog.
  3. 20 private copies of the stack per pod, with pods already at ~80% of their memory limits at concurrency 20.

The change

The ticket proposed three variants. Findings ruled out two of them, and the reasoning is recorded in full on UN-4136:

  • Variant 1 (move PromptServiceConstants out from under executor.executors) cannot work — line 321 pulls the package regardless, via executor/__init__.py.
  • Variant 3 (import in the parent before fork) is ruled out by an existing documented decision. pg_queue_consumer/supervisor.py's _run_child states it outright: "The worker import (and any connections it opens) happens HERE, in the child — never inherited across the fork — so each process owns its own connections."
  • Variant 2 (warm it in post-fork child init) works, but keeps all ~170 CPU-seconds and merely relocates them to pod start, delaying readiness and buying back no memory.

So this removes the cost instead of relocating it:

file change
executor/ package init Resolve celery_app lazily (PEP 562 module __getattr__). from executor import celery_app still works.
executor/executors/ package init Replace the eager import-side-effect registration with an explicit, idempotent register_all(). LegacyExecutor stays reachable from the namespace, lazily.
executor/worker.py, executor/tasks.py The two entrypoints that need a populated registry call register_all() directly.

Both entrypoints already carried an explicit import executor.executors # noqa: F401 for exactly this purpose, with comments saying so — this makes an existing intent explicit rather than changing it.

structure_tool_task.py is untouched: all three of its imports become cheap on their own.

Result

Same harness, same environment, before vs after:

deferred first-task cost legacy_executor loaded
before 2.334s True
after 0.004s False

2.33s rather than 8.5s locally because the page cache is warm and the cloud entry-point executors aren't installed on this machine — those were the +1.15s and +0.27s segments in the pod. The stack is no longer imported at all, so the whole segment goes away regardless of its size.

Tests

tests/test_executor_import_isolation.py (new) pins the import graph, not a duration — a timing assertion would be flaky on CI, while the thing that actually regresses is an eager import creeping back into either package init. Each case runs in a fresh interpreter so the rest of the suite cannot pre-import the stack and mask it.

Both cases were confirmed failing on origin/main:

AssertionError: eagerly imported: ['executor.worker', 'executor.executors.legacy_executor']
AssertionError: executor/__init__ built the app

The pre-existing tests/test_executor_registration.py — which already covers this ticket's "registry populated before the first task is claimed" bar, via the PG-consumer bootstrap path — still passes.

tests/test_plugin_loader.py is updated to the explicit-registration contract, plus a case worth calling out. ExecutorRegistry.register raises ValueError on a name already registered, and every cloud plugin imports LegacyExecutor (e.g. lookup_enrichment/src/base.py:13), so a plugin pulled in ahead of register_all() fires that decorator first. register_all() is safe because it registers through module imports rather than an explicit register(...) call, so sys.modules caching stops the decorator running twice — but that is load-bearing and now pinned by test_register_all_tolerates_legacy_executor_already_imported. Had it been written the other way, the second call would raise and take the executor worker down at startup.

Not verified here

  • End-to-end convergence on staging (9.08s -> ~0.39s local-work segment). Needs a deploy; this PR verifies the import graph and the local import cost.

  • Cloud entry-point executors are not installed locally, so registration is verified for legacy only — worth exercising on staging.

    What is verified: none of them relied on the removed side effect. All 8 entry points in the unstract.executor.executors group (table, lookup_test, single_pass_extraction, simple_prompt_studio, smart_table, agentic, line_item, agentic_table) were swept for bare-package imports, including function-local ones — that being how the original bug hid. There are none. Every spelling used across all 8 is a submodule import (executor.executors.constants, ...legacy_executor, ...answer_prompt, executor.executor_tool_shim, and six more), so nothing depended on import executor.executors registering anything.

PR_TYPE

FIX, on the QA test in the workspace CLAUDE.md. This is not infra or CI — it changes the registration path for the executors that do all extraction work. If register_all() were wrong, extraction fails outright with No executor registered with name 'legacy'. That is squarely something QA must exercise against a running deployment, and the cloud entry-point executors above are the specific thing to exercise.

Out of scope, as recorded on the ticket: the CAST AI stability policy mis-assignment.

ritwik-g and others added 2 commits September 21, 2026 15:51
Each pg_queue_consumer child paid ~9s of lazy initialisation on its first
task. The supervisor preforks 20 children per pod, so that is ~170
CPU-seconds per pod spent during serving rather than at startup, which
spikes fleet CPU at load onset and inflates CAST AI's WOOP p99 to 792m
against a 200m request -- putting the HPA signal's ceiling below its own
target so the fleet collapses to minReplicas under backlog.

structure_tool_task.py imports two string constants and a StreamMixin shim
from the executor package. None needs the executor registry, but every
import under `executor` booted the whole thing: executor/__init__.py
imported .worker, which imported executor.executors, which imported
LegacyExecutor and ran entry-point discovery. Measured at 9.98s cold.

Resolve celery_app lazily (PEP 562) and replace the executors package's
import-side-effect registration with an explicit, idempotent register_all().
The two entrypoints that need a populated registry -- executor/worker.py and
executor/tasks.py -- call it directly; both already carried an explicit
`import executor.executors  # noqa: F401` for exactly this purpose, so this
makes an existing intent explicit rather than changing it.

structure_tool_task.py is untouched; all three of its imports become cheap.
Deferred first-task cost goes 2.334s -> 0.004s locally, with
legacy_executor no longer imported into file_processing at all.

The new test pins the import graph rather than a duration -- a timing
assertion would be flaky on CI, while what actually regresses is an eager
import creeping back into either package init. Both cases were confirmed
failing on origin/main.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Critical: restore the celery.app.trace suppression. executor/worker.py's
setLevel(WARNING) mutes the trace logger, which prints the full task result —
for execute_extraction, extracted customer document text — at INFO. That module
is on no deployed path: workers/worker.py exec-loads executor/tasks.py by path
for both executor roles and nothing runs `celery -A executor`. It ran only
because executor/__init__.py eagerly imported .worker, so making that lazy
silently re-enabled result logging on every extraction. Moved the suppression to
executor/tasks.py, which keeps it without re-importing the expensive module.

register_all(): latch before discovering rather than after. ep.load() executes
third-party code, and a plugin whose import graph reaches back into the function
would restart the entry point loop, nesting once per level (measured: 50 nested
loads where 1 was intended). The import-side-effect version got that safety free
from sys.modules. A discovery that raises now resets the latch instead of
pinning a permanently-empty list.

Correct the docstrings rather than the behaviour where the claim was the defect.
register_all() cannot repopulate a registry something else emptied — the second
import is a sys.modules hit and the decorator does not re-run. Making it
re-register explicitly broke four test files that do unguarded
ExecutorRegistry.register(LegacyExecutor) after a clear, so the guarantee is now
stated as per-process instead. Also: executor/__init__.py no longer contradicts
its sibling in the present tense; worker.py's register_all() call is gone (it
could never do work — import executor.tasks above it already registered, so the
comment claiming otherwise would have led a maintainer to delete the call that
matters); plugins/loader.py and test_plugin_loader.py's own enumeration no
longer describe the removed import side effect.

Tests: all three new cases were defective. One was vacuous — removing the latch
entirely left the suite green, because with no cloud plugins installed the first
call also returns []. Two passed only on suite ordering: an unrelated module
writes ExecutorRegistry._registry["legacy"] directly and never cleans up, and CI
shards with xdist's default per-test scheduler, so their result depended on shard
assignment. Rewritten to assert registration in a fresh interpreter, to assert
idempotency on a spy's call count rather than a return value that is [] either
way, and to snapshot/restore the process-global registry. Added coverage for
re-entrancy, for the non-latching failure path, and for the trace suppression.
All five guards are mutation-verified.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review round 1 — unstract:standard-review (INITIAL)

Round 1 tip: bf3cab1b04ba1702f7ffd220a505b92a01a5bd4a · base 53269a1f · 6 files / 264 lines
Verdict: BLOCK — 1 Critical, 2 High, 5 Medium, 4 Low.

All findings below are fixed in 98965e12e. Every one was reproduced locally before being acted on; agent reports were not taken at face value.


🔴 Critical — Lens 4/10 · the lazy import silently removed a PII control

executor/worker.py carries a deliberate logging.getLogger("celery.app.trace").setLevel(logging.WARNING), commented "the trace logger prints the full result dict on task success, which can contain sensitive customer data (extracted text, summaries, etc.)".

That module is on no deployed pathworkers/worker.py exec-loads executor/tasks.py by path for both executor roles, and nothing launches celery -A executor. It ran only because executor/__init__.py eagerly imported .worker, which is exactly the import this PR made lazy.

Reproduced by running what the deployed executor actually loads (import executor.tasks):

BEFORE (origin/main)   executor.worker imported: True    celery.app.trace: WARNING
AFTER  (round-1 tip)   executor.worker imported: False   celery.app.trace: NOTSET

NOTSET inherits the celery logger at LOG_LEVEL (default INFO), so every execute_extraction success would have logged up to ~1KB of the extracted-document payload to stdout → Cloud Logging. It is the only celery.app.trace suppression in the repo.

Fix: moved the suppression to executor/tasks.py. Verified it is restored (WARNING) without re-importing executor.worker — so the performance win is unaffected. Pinned by test_loading_executor_tasks_suppresses_celery_result_logging.


🟠 High — Lens 13 · all three new tests were defective

  • Vacuous. Removing the _registered latch outright left test_plugin_loader.py green at 30/30 — with no cloud plugins installed the first call also returns [], so assert register_all() == [] cannot distinguish a working latch from none.
  • Order-dependent. test_log_streaming.py writes ExecutorRegistry._registry["legacy"] directly and never cleans up; it sorts between a module that clears the registry and test_plugin_loader.py. Reproduced: pytest tests/test_line_item_extraction.py tests/test_plugin_loader.py2 failed. CI shards with xdist's default per-test scheduler (tests/rig/cli.py:1201, no --dist loadfile), so the result depended on shard assignment.

Fix: registration is now asserted in a fresh interpreter; idempotency on a spy's call count rather than a return value that is [] either way; an autouse fixture snapshots and restores the process-global registry. Added re-entrancy, failure-path and trace-suppression cases. All five guards mutation-verified (M1–M5 each killed).

🟠 High — Lens 16 · worker.py's comment was inverted

Its register_all() could never do work: import executor.tasks on the line above already registered. Trace: _registered FalseTrue after import executor.tasks → second call returns []. The comment framed it as the populating call and tasks.py's as the harmless duplicate — the reverse. A maintainer trusting it could delete tasks.py's call and break the PG executor path with exactly the failure that code was written to prevent.

Fix: removed the dead call; import executor.tasks documented as the real registrar.


🟡 Medium

Lens Finding Fix
14/6 Re-entrancy. Latch set after discovery, so a plugin reaching back into register_all() during ep.load() restarts the entry-point loop. Measured 50 nested loads where 1 was intended; the import-side-effect version got this free from sys.modules. Latch before discovering
8 A discovery that raises latched a permanently-empty list except BaseException resets it; retry works
7/16 register_all()'s docstring promised a postcondition it cannot meet — it cannot repopulate a cleared registry (second import is a sys.modules hit) Docstring narrowed to the per-process guarantee — see note below
16 executor/__init__.py stated, present tense, the cost model this PR removes, contradicting its sibling docstring Past-tensed
7 LegacyExecutor lost its static type — no TYPE_CHECKING counterpart, unlike celery_app Added

Note on the postcondition finding. My first fix made register_all() repopulate a cleared registry. That broke test_line_item_extraction with ValueError: Executor name 'legacy' is already registered — four test files do unguarded ExecutorRegistry.register(LegacyExecutor) after a clear. On origin/main that pairing passes, so it was a real regression. Reverted: the defect was the claim, not the mechanism, and fixing the docstring doesn't touch four unrelated files.

⚪ Low

_MUST_NOT_LOAD's rationale was backwards (legacy_executor imports the x2text adapter stack at module scope — it is the expensive module); a cloud-repo path cited as if local; unreproducible pod-name/prefork-count measurements trimmed to one anchored figure; lens 17plugins/loader.py and test_plugin_loader.py's own "Verifies" enumeration both still described the removed import side effect.


Examined and clean

Lens 17 branch-currency: base has not advanced (git log $MB..origin/main empty, exit 0). Lens 7 consumer search across all five workspace repos: zero consumers of the changed surface beyond the files updated here; all 8 unstract.executor.executors entry points import submodules only, never the bare package, including function-local imports. Boundary sweep: 2 sinks traced (both AttributeError messages, internal identifiers only), clean. Lens 15: all pinned pre-commit hooks pass and modified nothing. Lens 6 across fork(): no divergence — the supervisor imports in the child deliberately. Lens 11: no readiness window — registration precedes the @worker_task decorator. Lens 19: the change only shortens the heartbeat freeze, moving oldest_age() away from the staleness threshold. Lens 14: no build/Docker/CI dependency on the old side effect.

Coverage gaps, stated rather than glossed

  • Lens 3 not covered — the dispatched agent returned a completion signal but no report body. Re-requested; not recorded as clean.
  • Cloud entry-point executors are not installed locally, so registration is exercised for legacy only. Their independence from the removed side effect is verified by source sweep, not by running them.
  • Step 2a security deep-dive not triggered: no endpoint, parser, authn/authz path, untrusted-input sink or credential in the diff.

cost: agents=8 wall=~42:00 lenses-examined=18 of 19 files=6 lines=264

Round 2 is owed on the delta these fixes create — git diff bf3cab1b0 98965e12e, 7 files / +257−96, entirely unreviewed.

ritwik-g and others added 2 commits September 21, 2026 16:31
_reset_discovery_for_tests()'s docstring still claimed register_all()
re-registers the bundled executor when the registry has lost it. That was true
of a fix I reverted — making register_all() re-register broke four test files
that do unguarded ExecutorRegistry.register(LegacyExecutor) after a clear — and
the stale claim then contradicted register_all()'s own docstring. It sat on the
helper a test author reads before writing `clear(); reset(); register_all()`
and asserting on the registry, which returns cleanly and leaves it empty.

register_all() now returns a copy. It was handing out the latched module list,
so a caller appending to the result corrupted what every later caller saw.

Subprocess test snippets raise SystemExit instead of asserting. The child
inherits the parent environment, so under PYTHONOPTIMIZE every assert is
stripped: the snippet would print OK, exit 0, and the guard would pass having
checked nothing. Verified by mutating the trace suppression under
PYTHONOPTIMIZE=1 and confirming the test still fails.

Test coverage for three guards that had none: the LegacyExecutor __getattr__
shim (no caller in this repo or the cloud plugins uses that spelling, so a typo
in the name comparison would break every cloud plugin while OSS CI stayed
green), the breadth of the BaseException catch (the failure test drove only
RuntimeError, so narrowing the handler would have kept the suite green while
reopening the latched-empty window for the KeyboardInterrupt case it exists
for), and the copy semantics above.

Documented, not fixed: discover_executors() catches per-entry-point failures and
logs a warning, so register_all() latches [] and returns it whether no cloud
plugins are installed or every one of them failed to import. A broken plugin
wheel therefore boots clean and surfaces per-request as "No executor registered
with name 'table'". The Returns section now states both meanings rather than
only the benign one. Making the aggregate loud is a behaviour change to the
loader and out of scope for a performance PR.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every finding this round was introduced by round 1's fixes.

Drop the reset-on-failure rather than patch it. It was added so "a caller that
survives the failure retries instead of silently getting a permanently empty
list", but there is no such caller — the single call site is at
executor/tasks.py module scope, so a failure kills the boot and the process
restarts with clean module state. Worse, the retry it enabled is harmful: a
plugin whose @ExecutorRegistry.register fires before it raises is evicted from
sys.modules but left in the registry, so re-importing it raises a duplicate-name
ValueError that discover_executors swallows into a warning, leaving that
executor silently absent for the life of the process — the exact outcome the
reset claimed to prevent. Verified. Dropping it also closes the window where the
latch was armed one statement before the try, and removes the BaseException
breadth question along with the test that pinned it.

Stop the test fixture corrupting the registry it was added to protect.
clear() + update(saved) deletes a registration the test itself caused: when this
class is what first imports legacy_executor, the decorator fires during the test
so `legacy` is absent from the snapshot, and it cannot be restored because the
re-import is a sys.modules hit. Every later test in that process then saw an
empty registry; nothing failed only because five other modules re-register
defensively. Now an additive restore.

Restore test_register_all_tolerates_legacy_executor_already_imported, deleted in
round 1. Its scenario is live in production — a cloud plugin imports
LegacyExecutor at module scope and is loaded by ep.load() inside discovery — and
the subprocess replacement covers the opposite ordering, a fresh process with no
prior import. Deleting it weakened coverage for a real path.

Convert the remaining subprocess snippet to raise SystemExit. Round 1 wrote that
rule into one test file and violated it in the other in the same commit; under
PYTHONOPTIMIZE the asserts are stripped and the guard passed having checked
nothing. Confirmed by mutation under PYTHONOPTIMIZE=1.

Docstrings: the module docstring still described a register_all() call in
executor/worker.py that the same commit deleted, and tasks.py still called that
second call "harmless" — both now say executor/tasks.py is the only caller and
that worker.py's import of it is load-bearing. The Returns block promised the
names "on every call" when the re-entrant call it advertises returns the empty
placeholder, and counted two causes for an empty list when there are three. The
claim that tests clearing the registry re-register explicitly was false for four
of the five that clear it. The xdist claim is narrowed to the repo's test rig,
which is what is checkable — there is no .github/workflows here.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review round 2 — unstract:standard-review

Round 2 tip: ec0d2ad57456ec91cdd2bd6419fbab5fefd76a26 · delta bf3cab1b0..ec0d2ad57 (7 files, +315/−99)
Verdict: BLOCK — 1 High, 9 Medium, 4 Low. Every finding was introduced by round 1's fixes.

All fixed in fdad45bad. Each was reproduced locally before being acted on.


🟠 High — the module docstring described a call site the same commit deleted

executors/__init__.py still said "executor/worker.py calls it too… The call is idempotent and re-entrant, so the order they run in does not matter." Round 1 deleted that call. There is now exactly one caller, and executor/worker.py reaches it only through import executor.tasks — an import carrying # noqa: F401, i.e. flagged to linters as unused. A maintainer trimming it on the docstring's authority gets a worker that boots clean and fails every extraction with "No executor registered".

Found independently by four of the six agents. Fixed in both that docstring and tasks.py's matching claim.

🟡 The reset-on-failure was net-negative — removed, not patched

Round 1 added except BaseException: _cloud_executors = None; raise so "a caller that survives the failure retries". There is no such caller — the one call site is at module scope, so a failure kills the boot and the process restarts clean. And the retry it enabled is harmful:

1. first import raised : KeyboardInterrupt
2. left in sys.modules : False        ← module evicted
3. left in registry    : ['tbl']      ← but its decorator already fired
4. the RETRY raises    : ValueError - Executor name 'tbl' is already registered

That ValueError is an Exception, so discover_executors swallows it into a warning and the executor is silently absent for the life of the process — the exact outcome the reset's own comment claimed to prevent. Dropping it also closed the window where the latch was armed one statement before the try, and retired the BaseException breadth question.

🟡 The fixture added to prevent registry pollution was causing it

_isolate_global_state did clear() + update(saved). When this class is what first imports legacy_executor, the registration happens during the test and so is absent from the snapshot — teardown deleted it, unrecoverably (the re-import is a sys.modules hit). Probe:

registry after TestExecutorsInit: []
--- with clear() dropped ---
35 passed

Nothing failed today only because five other modules re-register defensively. Now an additive restore.

🟡 A deleted test weakened live coverage — restored

Round 1 deleted test_register_all_tolerates_legacy_executor_already_imported. Its scenario is live: a cloud plugin imports LegacyExecutor at module scope and is loaded by ep.load() inside discover_executors(). The subprocess replacement covers the opposite ordering — a fresh process, no prior import — so it cannot stand in. This tripped the "no existing test weakened" hard gate.

🟡 The PYTHONOPTIMIZE rule was written in one file and violated in the next, same commit

Round 1 converted three snippets in test_executor_import_isolation.py to raise SystemExit and documented why — then added a new subprocess snippet in test_plugin_loader.py using bare assert. Under -O those are stripped: the child prints OK, exits 0, and the guard passes having checked nothing. Confirmed by mutation under PYTHONOPTIMIZE=1. Found by three agents independently.

Remaining Medium/Low

Returns: promised the names "on every call" while the re-entrant call it advertises returns the empty placeholder, and counted two causes for [] when there are three. The class docstring justified the snapshot by a registry read no case in the class performs. "Tests that clear the registry re-register explicitly" was false for four of the five that do. The deferral pointing at UN-4136 was dangling — the note now exists on the ticket. Path-root convention was inconsistent within one docstring.


Gates

Mutation gate: 6/6 killed, including the bundled-registration mutant under PYTHONOPTIMIZE=1 — which is what proves the SystemExit conversion actually closed the hole.

An independent mutation matrix over 11 mutations found every named behaviour pinned by a test that fails when broken, and no behaviour in the delta without a test.

Lens 15: all pinned pre-commit hooks pass, modified nothing. Lens 17: rename fully propagated, no stale restatements. Step 2z: the delta's only sink change is a subtraction. Lens 4 / Step 2a: no trigger — no endpoint, parser, authn path, untrusted input or credential.

Verified independently: the Critical fix from round 1 survives the real import worker bootstrap — celery.app.trace is WARNING, executor.worker stays unimported, legacy registered. _configure_celery_loggers does not touch that child logger and a parent's level does not override an explicit child's.

Corrections to round 1's report

  • The latch/try window was reported Medium, "pins permanently". Overstated — plugins.loader is already in sys.modules by that line, so only an async signal can land there. Low.
  • The class docstring claimed "CI shards with xdist". Not established: there is no .github/workflows/ in this repo. Narrowed to the repo's test rig, which is checkable.

cost: agents=6 lenses-examined=18 of 19 files=7 lines=414

Round 3 is owed on ec0d2ad57..fdad45bad — 3 files, +70/−64.

The lazy import moved WHEN legacy_executor first loads, and two test files
depended on the old timing. On main, importing executor.executors registered
`legacy` at package-import time — before any fixture cleared the registry — so
an unguarded ExecutorRegistry.register(LegacyExecutor) in the test always
followed a cache hit. With the import lazy, that first import now happens inside
the test, after the clear, and the explicit registration raises a duplicate-name
ValueError. tests/test_legacy_executor_index.py and
tests/test_legacy_executor_extract.py fail alone on this branch and pass alone
on main; they survive the full alphabetical suite only because an earlier module
imports the class first, which is why this looked pre-existing. Guarded both,
matching the pattern eight sibling modules already use.

Restore the except BaseException reset removed in round 2. That removal rested
on a false premise: the handler never retried, it re-raised unchanged, and its
only effect was to stop a LATER call reporting "no cloud executors" as though
discovery had succeeded. It also preserves pre-PR behaviour — discovery ran as a
package import side effect, and a module that raises mid-import is evicted from
sys.modules, so the next import re-ran it. The duplicate-registration hazard
cited as the reason to drop it applies to re-running discovery at all, so it
predates this function and was never traded away by keeping the reset.

Restore the clearing teardown, also removed in round 2. It was made additive to
stop it deleting a registration the class itself caused, but nothing in the
suite depends on import-side-effect registration of `legacy` — every consumer
registers explicitly or assigns directly — so the snapshot restore was correct
isolation and the additive version leaked `legacy` into every later test in the
worker. Worse, the one test that ran unpatched discovery would, where the cloud
wheels are installed, have leaked all eight cloud executors into names the
sibling modules register themselves.

That test is rewritten: it arranges its own precondition instead of inheriting
one, patches discovery so it stops doing real entry-point loading, and asserts
only the property it is named for. As written it was order-dependent — it failed
deterministically after any module that clears the registry — and its registry
assertion was satisfied by its own import line rather than by register_all().

Docstrings: "the only caller" was false (the suite calls it in-process) and the
handler-removal argument was reasoned from it; restored the "production"
qualifier and the note that executor/worker.py's app is test-only. The stated
failure symptom for a broken plugin wheel held only when the plugin raises
before its decorator. Cited the rig path repo-root-relative.

All six guards mutation-verified.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review round 3 — unstract:standard-review

Round 3 tip: fdad45bad51cf18c3a418cc1223cf55591ad7252 · delta ec0d2ad57..fdad45bad (3 files, +70/−64)
Verdict: BLOCK — 3 High, 4 Medium, 5 Low. All fixed in 6156593a8.

The important one was not in the delta under review — it is a regression in the PR's core change that three rounds had missed.


🔴 A regression in the core change: the lazy import moved when registration fires

ExecutorRegistry.register raises on a duplicate name. Several test modules clear the registry in an autouse fixture and then call ExecutorRegistry.register(LegacyExecutor) unguarded. That was safe only by accident: executor.executors registered legacy at package-import time, so the class was always already in sys.modules before any fixture ran, and the explicit call followed a cache hit.

Making that import lazy moves the first import inside the test, after the clear — so the explicit registration now collides:

tests/test_legacy_executor_index.py    branch:[1 failed]  main:[0 failed]
tests/test_legacy_executor_extract.py  branch:[1 failed]  main:[0 failed]

Both pass in the full alphabetical suite, because an earlier module imports the class first. That is why it read as pre-existing. It is not: under xdist's per-test scheduler, or running a single file, they fail. Guarded both with the if "legacy" not in ExecutorRegistry.list_executors(): pattern that eight sibling modules already use.

This is the second side effect in this PR whose timing mattered. The first was the celery.app.trace suppression (round 1's Critical). Both are the same lesson: making an eager import lazy silently relocates every side effect it was carrying.


🟠 Two round-2 decisions reversed — both were over-corrections

Restored except BaseException: _cloud_executors = None; raise. I removed it in round 2 arguing the retry it enabled was harmful. The premise was wrong twice over: the handler never retried — it re-raised unchanged, and only stopped a later call reporting "no cloud executors" as though discovery had succeeded. And it preserves pre-PR behaviour: discovery ran as a package import side effect, and a module that raises mid-import is evicted from sys.modules, so the next import re-ran it. The duplicate-registration hazard I cited applies to re-running discovery at all, so it predates this function.

Restored the clearing teardown. I made it additive in round 2 to stop it deleting a registration the class caused. But nothing in the suite depends on import-side-effect registration of legacy — every consumer registers explicitly or assigns directly — so the snapshot restore was correct isolation, and the additive version leaked: measured BEFORE: [] → AFTER: ['legacy']. Worse, the one test running unpatched discovery would, where the cloud wheels are installed, have leaked all eight cloud executors into names the sibling modules register themselves.

🟠 The test restored in round 2 was both fragile and vacuous

Order-dependent — failed deterministically after any registry-clearing module:

pytest tests/test_single_pass_extraction.py tests/test_plugin_loader.py::TestExecutorsInit
→ AssertionError: expected exactly one 'legacy': []

And its registry assertion was satisfied by its own import line, not by register_all(): with the bundled import mutated out, it still passed. Rewritten to arrange its own precondition, patch discovery, and assert only the property it names.

🟡 Medium / Low

"the only caller" was false — the suite calls it in-process — and the handler-removal argument was reasoned from it. Restored the production qualifier and the note that executor/worker.py's app is test-only. The stated failure symptom for a broken plugin wheel held only when a plugin raises before its decorator; the other ordering leaves a poisoned entry in the registry. Rig path cited repo-root-relative. count(...) == 1 replaced — it can only ever be 0 or 1.


Gates

Mutation gate: 6/6 killed. Notably P5 — injecting an unconditional re-registration into register_all() fails 8 tests.

Full suite, no agents active: 1334 passed, 12 pre-existing failures, zero regressions.

Lens 15 all pinned hooks pass, nothing modified. Lens 17 clean — registration described consistently in four places. Step 2z: no sink change in this delta. Lens 4 / Step 2a: no trigger.

All four module-level side effects of executor/worker.py, enumerated

Prompted by a reviewer asking what else the eager import was carrying:

Effect Disposition
L17 WorkerLogger.setup(EXECUTOR) Not lostworker.py:52 configures for the selected type before exec-loading tasks.py, and configure is _configured-latched. Verified: logging state byte-identical to main after the real bootstrap
L18 build_celery_app(EXECUTOR) The cost being removed
L23 celery.app.trace suppression Round 1's Critical — nothing else replicated it
L64 register_health_check Harmless — its only reader is reachable solely from the Celery build path; the PG role runs its own LivenessServer

Correction to round 1's and round 2's reports

I described the Critical as the class of thing the ticket's three variants would all have hit. That is wrong: variant 1 never touches executor/__init__.py, and variants 2 and 3 keep the import. The hazard belongs to this approach, and to any future trimming of that import.

cost: agents=5 lenses-examined=18 of 19 files=3 lines=134

Round 4 is owed on fdad45bad..6156593a8 — 4 files, +120/−55.

Guard the two remaining duplicate-registration sites. test_legacy_executor_
scaffold.py is a proven regression from this branch, not pre-existing as an
earlier check concluded: run alongside the index and extract modules it passes
on main (52 passed) and fails here. My earlier check ran that file ALONE, where
it is broken on main too for its own reason, which hid the difference. Mutation-
verified: removing the guard reproduces the failure in that ordering.
test_plugin_migration_regression.py has the identical shape; its guard is
defensive consistency and is NOT pinned by a test — that file's own earlier
tests import legacy_executor before the helper runs, so no ordering within it
exercises the guard. Recorded rather than claimed as covered.

"This does not retry" was true of the handler and false of the mechanism:
un-arming the latch is exactly what lets the NEXT call re-run discovery, and the
comment then justified itself by saying the next import re-ran it. Both cannot
hold. It now says plainly that it re-arms a retry for a later caller, that the
re-run is not free when a plugin registered before it raised — that plugin ends
up live in the registry but absent from the returned list — and that no
production caller survives the re-raise to make that call.

The Returns block now says a non-empty list can under-report the registry after
a failed-then-re-run discovery. The _cloud_executors sentinel comment said None
means "discovery has not run"; since the handler came back it also means "ran
and raised". The empty-list placeholder moved inside the try, so an async signal
between the assignment and the try cannot leave it armed. The register_all
docstring no longer offers test_legacy_executor_scaffold.py as "the pattern to
copy" — that file carried the unguarded helper this commit fixes; it states the
guard rule directly instead.

TestExecutorsInit's docstring claimed the fixture leaves the process as it found
it. sys.modules is not restorable and this class can be the first importer of
legacy_executor, which is the state the sibling guards exist to survive.

test_failed_discovery_un_arms_the_latch is parametrised rather than looped: a
for loop stops at the first failing arm, and the KeyboardInterrupt arm is the
one the docstring argues is the point. Its docstring no longer says only
BaseException reaches the handler — an entry_points() failure is an ordinary
Exception and reaches it too, which is what the RuntimeError arm covers.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review round 4 — unstract:standard-review

Round 4 tip: 6156593a872023a96907b9c7615d7dfe0fe8ad56 · delta fdad45bad..6156593a8 (4 files, +120/−55)
Verdict: BLOCK — 2 High, 6 Medium, 2 Low. All fixed in 0541d0512.

⚠️ This round is INCOMPLETE and is not scored as clean. Two of five dispatched agents terminated mid-run on a session rate limit. See Coverage below.


🟠 High — a regression I had wrongly closed as pre-existing

test_legacy_executor_scaffold.py carries the same unguarded
ExecutorRegistry.register(LegacyExecutor) after a fixture clear() that this
branch already had to guard in two sibling files.

I checked it in round 3 by running that file alone, saw it fail on main too,
and closed it. That was the wrong experiment: the failure is an interaction, so
the comparison has to preserve the interaction. Run alongside the siblings:

scaffold + index + extract:
  MAIN    52 passed
  BRANCH  1 failed, 51 passed   ← test_legacy_in_registry

Scaffold-alone is broken on main for its own unrelated reason, which masked the
difference. The branch genuinely breaks it in the configuration the guard exists
for. Guarded, and mutation-verified: removing the guard reproduces the
failure in that ordering (52 passed → 1 failed).

test_plugin_migration_regression.py has the identical shape and is guarded too,
for consistency — but see Gates: that one is not pinned by a test.

🟠 High — "This does not retry" was false of the mechanism

The restored handler's comment claimed it does not retry. Un-arming the latch is
exactly what makes the next register_all() re-enter if _cloud_executors is None and re-run the entry-point loop — and the comment then justified itself by
saying "the next import re-ran it". Both cannot hold.

Now states plainly that it re-arms a retry for a later caller; that the re-run is
not free when a plugin registered before it raised (that plugin ends up live in
ExecutorRegistry but absent from the returned list, because
discover_executors swallows the duplicate-name ValueError into a warning);
and that no production caller survives the re-raise to make that call.

🟡 Medium / Low

The Returns: block now records that a non-empty list can under-report the
registry after a failed-then-re-run discovery. The _cloud_executors sentinel
said None means "discovery has not run"; with the handler back it also means
"ran and raised". The [] placeholder moved inside the try, closing a window
where an async signal could leave it armed. The register_all docstring no
longer offers test_legacy_executor_scaffold.py as "the pattern to copy" — that
file carried the unguarded helper this commit fixes. TestExecutorsInit's
docstring claimed the fixture leaves the process as it found it; sys.modules is
not restorable, and this class can be the first importer.
test_failed_discovery_un_arms_the_latch is parametrised rather than looped — a
loop stops at the first failing arm, and the KeyboardInterrupt arm is the one
the docstring argues is the point — and no longer claims only BaseException
reaches the handler, since an entry_points() failure is an ordinary
Exception.


Gates

Mutation gate: 4/5 killed, 1 unpinned and recorded as such.

Mutant Result
reset handler removed KILLED
BaseExceptionException KILLED
[] placeholder removed KILLED
scaffold guard removed KILLED (with scaffold-first ordering)
migration-regression guard removed SURVIVED

The last one is honest rather than fixed: that file's own earlier tests import
legacy_executor before the helper runs, so no ordering within it exercises
the guard. It is defensive consistency against a latent instance of a class
proven live elsewhere, and is recorded as unpinned rather than counted as
covered.

Full suite: 1335 passed, 12 pre-existing failures, zero regressions.
Lens 15 pinned hooks pass. Lens 17 clean. Step 2z: no sink change. Lens 4 / 2a:
no trigger.

Coverage — two lenses NOT covered this round

  • Lenses 1, 2, 3, 9, 10, 15 (correctness agent)Not covered — agent terminated on a session rate limit mid-run.
  • Lens 13 (test-analysis agent)Not covered — agent terminated on a session rate limit mid-run.

Lens 3 was covered by the dedicated silent-failure agent, which produced two of
this round's findings. Lens 13 was partially covered by the cold read. Neither
substitutes for the dispatched specialist, so both are recorded as gaps rather
than inferred clean.

Round 5 runs a full fan-out over 6156593a8..0541d0512, which subsumes the
uncovered lenses and reviews this round's fixes together. Re-dispatching the
dead agents against the old delta would review a scope that no longer exists.

One check that came back clean for the first time

The cold read was asked to verify a count-of-its-own-enumeration claim against
its constraining sentence. It enumerated all seven methods in TestExecutorsInit
and concluded: "Count = 1. The claim holds. I could not break it."

cost: agents=5 (2 died) lenses-examined=16 of 19 files=4 lines=175

ritwik-g and others added 2 commits September 22, 2026 00:31
…g prose

tests/test_line_item_extraction.py was the last site with the hazardous
ordering — autouse fixture clears the registry, then the test imports
legacy_executor and registers it unguarded. When that import is the first in the
process the decorator fires after the clear and the explicit register raises.
Fails alone on this branch, passes alone on main. Routed through the module's
existing guarded helper. Mutation-verified. An enumeration of all 22
ExecutorRegistry.register sites under workers/tests/ says this was the last one
of its shape: 13 already guard, 2 import before they clear and are safe by
ordering.

Delete the historical narrative from the guard comment in all four files. It was
written for index/extract, where it is true, and copied into scaffold and
migration_regression, where it is false: scaffold fails alone at the merge base,
so it was never "unguarded safely", and in both of those the first import of
legacy_executor was already inside a test before UN-4136, so UN-4136 moved
nothing. Only the rule survives, which is true everywhere.

The prose in register_all() has now been wrong in four consecutive rounds, each
time by being true of the case it names and false of one it never mentions, so
this trims rather than rewrites:
- the guard rule stated one implementation as mandatory and gave a reason that
  inverted the actual cause (the hazard is the import has NOT happened yet);
  it now states the invariant, and notes clearing immediately before is equally
  valid, which is what test_summarize_operation.py does;
- the under-report note was scoped to a non-empty list, but with a single
  installed plugin the re-run returns [] — folded into the empty-list
  enumeration as "at any length, including zero";
- the handler comment claimed every already-registered plugin is evicted from
  sys.modules. Only the module that was mid-import when the exception escaped
  is. Narrowed, and the surrounding argument cut to what is checkable.

Also reverts formatter churn this branch introduced in two test files: a
`ruff format` run reflowed an aligned comment block unrelated to the guard.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ollow-up

TestExecutorsInit's docstring said the sibling guards "assume" the sys.modules
entry for legacy_executor is left behind. They assume the opposite: the guard is
INERT when the entry is present (the import is a cache hit, the decorator does
not fire, the cleared registry has no `legacy`, the register runs) and
load-bearing when it is absent. The previous commit fixed this same inversion in
register_all()'s docstring and missed this sentence.

Record in code that the guard idiom is hand-copied across 23 sites in 18 files,
in three spellings, and that folding it into a shared test helper is the
follow-up rather than something this PR does. Counted: 23 guard sites, 7 direct
_registry["legacy"] assignments, 1 clear-then-register. 18 of the 23 predate
this branch. Detail on UN-4136.

Correcting the record on the previous commit: it stated the
test_plugin_migration_regression guard is "NOT pinned by a test". That is wrong.
No whole-FILE run pins it, but three class subsets fail without it, and the rig
runs xdist with the default per-test `load` scheduler, so the unit of ordering
in CI is the node. Measured with the guard removed:

  whole-file:  31 passed        <- what the mutation gate was measuring
  node-level:  3 classes fail   <- the guard is load-bearing

That wrong granularity is also why the test_line_item_extraction site survived
four rounds of module-ordering A/B: a defect that only appears when a node runs
without its file-mates is invisible at module granularity. Mutation checks on
the remaining guards were re-run at node level.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review round 5 — unstract:standard-review

Round 5 tip: 0541d0512b04e475c7bd614d2102c3c970bc10d9 · delta 6156593a8..0541d0512
Verdict: BLOCK — 1 High, 3 Medium, 2 Low. All fixed in a0d4c2e31 and 3dc477cea.

This round's most important finding is about my verification method, not the code.


🔴 The method was measuring at the wrong granularity

My mutation gate and every A/B comparison across four rounds ran at module
granularity. The rig runs pytest-xdist with the default per-test load
scheduler — tests/rig/cli.py:1201 appends -n <workers> with no --dist, and
tests/groups.yaml inherits parallel: true — so the unit of ordering in CI is
the node.

Measured on this branch with one guard removed:

whole-file run:  31 passed          ← what the gate was measuring
node-level run:  3 classes fail     ← what CI actually produces

That single wrong granularity caused both of my errors:

  1. A false disclosure. The round-4 commit stated the
    test_plugin_migration_regression guard is "NOT pinned by a test". It is —
    three class subsets fail without it. Corrected in 3dc477cea rather than
    left standing in pushed history.
  2. A site that survived four rounds. test_line_item_extraction.py:411 was
    the last unguarded duplicate-registration site. It fails alone on this branch
    and passes alone on main. Module-ordering A/B structurally cannot see a defect
    that only appears when a node runs without its file-mates.

The corrected check, run in full: every node in all 21 registry-touching
modules, each in its own process.

ran 339 nodes, each in its own process
failures: 0

And against the merge base on the sensitive nodes:

node main branch
scaffold::TestRegistration 1 failed 1 passed
migration::TestMultiExecutorCoexistence 1 failed, 3 passed 4 passed
migration::TestGracefulDegradation 1 failed, 2 passed 3 passed
line_item::TestLineItemEndToEnd 1 passed 1 passed

The branch is strictly better than main at node granularity: three classes that
fail on origin/main pass here, none regress.


🟡 Recorded, not fixed — the rule is hand-copied ~30 times

The guard idiom appears 23 times across 18 files, plus 7 sites that assign
ExecutorRegistry._registry["legacy"] directly and 1 that clears immediately
before registering. Three spellings of one rule; 18 of the 23 predate this
branch. Nothing enforces any of them — a new test module gets it right only by
remembering, and this PR's own five rounds are the evidence that it is
forgettable.

The fix is a shared register_legacy() helper (or an idempotent
ExecutorRegistry.register_once) so omission becomes impossible. That is a
test-infrastructure refactor across 23 files with its own blast radius, so it is
recorded rather than done: noted in register_all()'s docstring and detailed
on UN-4136.

🟡 Prose, cut rather than rewritten

TestExecutorsInit's docstring said the sibling guards "assume" the
sys.modules entry is left. They assume the opposite — the guard is inert
when the entry is present and load-bearing when it is absent. a0d4c2e31 fixed
this same inversion in register_all()'s docstring and missed this sentence;
3dc477cea closes it.

The round-5 commit also deleted the historical narrative from the guard comment
in all four files. It was written for index/extract, where it is true, and
copied into scaffold and migration_regression, where it is false — scaffold
fails alone at the merge base, so it was never "unguarded safely", and in both
files the first import was already inside a test before UN-4136.

Net for that commit: −91 lines against +73. After four rounds of prose being
wrong the same way — true of the case it names, false of one it never mentions —
the fix was subtraction, not a fifth rewrite.

⚪ Also fixed

Formatter churn this branch introduced: a ruff format run reflowed ~30 aligned
comment lines in two test files, unrelated to the guards and undeclared. Reverted;
guards re-applied clean. An independent AST comparison confirms the reflow changed
no assertion (33→33 and 53→53, structurally identical, no assert (cond, msg)
tuple trap).


Gates

Mutation gate, re-run at node granularity: both round-5 guards killed.
Independently, an agent verified the guards do not weaken their tests — mutating
LegacyExecutor.name makes the guard-skipping subsets fail, so the skip branch
is never vacuous.

Full suite: 1335 passed, 12 pre-existing failures, zero regressions.
Node sweep: 339/339 pass. Lens 15 pinned hooks pass; lint counts match main
exactly. Lens 17 clean. Step 2z: no sink change. Lens 4 / 2a: no trigger.

Round 6 is owed on 0541d0512..3dc477cea.

ritwik-g and others added 4 commits September 22, 2026 01:55
Both are the same failure this PR keeps repeating — saying more than is true —
so both are trimmed rather than restated.

The register_all() docstring claimed the guard rule is hand-copied across "~23
sites in three spellings". The 23 is the count of the guard IDIOM, but only 16
of those are followed by ExecutorRegistry.register; the other 7 assign
_registry["legacy"] directly, which cannot raise, so the rule does not reach
them. The count also excluded the third spelling's only site, so it matched two
of the three it claimed. Dropped: a tally of test-directory sites in a
production docstring rots on the next test added, and the duplication is the
fact worth recording, not the number.

It also pointed the follow-up at UN-4136, which is the ticket for THIS change —
cited as the problem being fixed at four other sites on the branch. A reader
following it lands on the ticket this PR closes and concludes the follow-up
shipped. The follow-up is recorded on that ticket as a comment and in the PR;
the docstring now just says it is deliberately left out.

TestExecutorsInit's docstring said the sibling guards are "inert when the entry
is present and load-bearing when it is not". True of the four modules whose
helper calls register(), false of the 7 direct-assign sites, where the guard is
inert in both states. The surviving sentence — the guards are correct either
way, so leaving the sys.modules entry is harmless — is true unconditionally.

Verified while checking this: all three touched test files now differ from
origin/main by their guard alone, with no formatter churn remaining.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… handler

Round 5's trim replaced a correct enumeration with a broader one that is false.
ep.load() sits inside discover_executors' per-entry-point `except Exception`
(loader.py:48-56), so an ordinary exception from a plugin never escapes — it is
logged and skipped there. Only a non-Exception BaseException, or a failure of
entry_points() itself, reaches the handler.

The risk is not the wording. A maintainer who believes anything out of ep.load()
reaches this handler has no reason to keep loader.py's per-entry-point catch,
and removing it turns "one broken plugin wheel is logged and skipped" into "one
broken plugin wheel aborts discovery" — on the executor worker's boot path. The
comment now says what that handler is for rather than enumerating what escapes.

Verified both ways: read against loader.py, and by driving a fake entry point
that raises RuntimeError — register_all() returns [] rather than raising.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…test

Third writing of this sentence, third distinct falsehood. The latest claimed an
ordinary exception from a plugin "never reaches here". It can: entry_points()
sits OUTSIDE discover_executors' per-entry-point try (loader.py:46 vs :48), so a
wheel with malformed entry-point metadata raises an ordinary Exception that
escapes straight to this handler. Reproduced by building a dist-info whose
entry_points.txt has a garbage line — TypeError, isinstance(Exception) True,
escapes discover_executors.

So the previous wording was wrong in the other direction from the one before it,
and the one before that was wrong differently again. Each rewrite was a fresh
surface for the same defect.

Replaced with a pointer to test_failed_discovery_un_arms_the_latch, which is
parametrised over both classes that can reach the handler — an ordinary
Exception out of entry_points(), and a non-Exception BaseException out of
ep.load() — and pins them by executing rather than by asserting in prose. Both
arms pass and report independently.

The correctly-scoped statement of what the per-entry-point handler absorbs
already lives in the module docstring, which says "failed to import" rather than
the broader "broken wheel" that made the last version false.

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two below-floor items from the final review round.

The pointer to the test that pins the BaseException choice gave a bare function
name, while the file's two other test references both carry paths. A pointer's
whole value is that it resolves, and a grep that comes back empty after a rename
is indistinguishable from a typo. It now reads
tests/test_plugin_loader.py::test_failed_discovery_un_arms_the_latch.

The test docstring named KeyboardInterrupt/SystemExit as the classes escaping
ep.load() while only KeyboardInterrupt had an arm. Rather than argue which
reading was intended, SystemExit now has one — it is free, since the handler
treats them identically. Verified it discriminates: with except BaseException
narrowed to except Exception, the RuntimeError arm passes (as it must) and both
the KeyboardInterrupt and SystemExit arms fail.

The docstring also stops saying "both classes", which was only true while there
were two arms, and now describes the partition instead: an ordinary Exception
escaping entry_points(), and the BaseException-not-Exception classes escaping
ep.load().

UN-4136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ritwik-g

Copy link
Copy Markdown
Contributor Author

Self-review rounds 6–8 — loop closed

Final tip: 986dc7aa6d3a90aad90b5430b137a077947d0b2b · Verdict: ready for review.

Round 8 returned no finding above the severity floor, and its delta contained
round 7's fixes. Both ready conditions met; all five hard gates hold.


Round 6 — 0541d0512..3dc477cea · 3 Medium, all prose, all fixed

My two new sentences were each over-specified. The guard-count "~23 sites in
three spellings" conflated the idiom (23) with the sites the rule reaches (16 —
the other 7 assign _registry["legacy"] directly and cannot raise). The
follow-up pointer named UN-4136, which is this PR's own ticket. And the
"inert / load-bearing" gloss was true of 7 sites and false of 16.

All three fixed by deletion. A tally of test-directory sites in a production
docstring rots on the next test added.

Round 7 — bcff70f62..2cae923da · 1 High, fixed

"an ordinary exception from a plugin never reaches here"false.
entry_points() sits outside discover_executors' per-entry-point try
(loader.py:46 vs :48), so a wheel with malformed metadata raises an ordinary
Exception straight into the handler. Reproduced by building a dist-info whose
entry_points.txt has a garbage line: TypeError, isinstance(Exception) true,
escapes.

That sentence had now been written three times and been wrong three times, each
version fixing its predecessor and introducing a new error:

# claim why it was wrong
1 "does not retry" un-arming the latch is a deferred retry
2 "anything at all out of ep.load()" ep.load() is inside except Exception
3 "an ordinary exception never reaches here" entry_points() is outside that try

So it was deleted rather than rewritten a fourth time, and replaced with a
pointer to the test that pins the behaviour executably. The risk was never the
wording: a maintainer believing version 3 had no reason to keep loader.py's
per-entry-point catch, and removing it turns "one broken wheel is logged and
skipped" into "one broken wheel aborts the executor's boot".

Round 8 — 2cae923da..526c2e389 · nothing above the floor

The surviving claim — "every class that reaches here" — was verified
by mutation, not by reading: narrowing except BaseException to
except Exception fails the KeyboardInterrupt and SystemExit arms while
RuntimeError passes, as it must. Two Low items taken: the pointer now carries
its file path (matching the file's own convention), and SystemExit has its own
arm rather than being named in prose without one.


Why the comment kept failing — the one durable lesson

Prose asserting what reaches an exception handler cannot be checked, so each
rewrite re-derived a reachability argument from memory and got a different part of
it wrong. A parametrised test asserting the same thing fails when it stops being
true
. Every round spent rewriting that paragraph was a round spent maintaining an
unverifiable claim.


Final state

Full suite 1336 passed, 12 pre-existing failures, zero regressions (baseline 1325)
Node sweep 339/339 — every node in all 21 registry-touching modules, each in its own process
Mutation gate every guard killed, at node granularity
Guards non-vacuous proven by mutating LegacyExecutor.name — the guard-skipping subsets fail
vs origin/main at node granularity strictly better — 3 classes that fail on main pass here, none regress
Pinned pre-commit all hooks pass, nothing modified; lint counts match main exactly

What the loop found that the change did not intend

  1. Critical — the lazy import silently dropped executor/worker.py's
    celery.app.trace suppression, which would have logged ~1KB of extracted
    customer text at INFO on every extraction.
  2. Re-entrancy — a plugin calling back into register_all() restarted the
    entry-point loop (50 nested loads vs 1).
  3. Registration timing — the lazy import moved when @ExecutorRegistry.register
    fires relative to test fixtures, breaking four test files.
  4. A pre-existing failure fixed incidentallytest_legacy_executor_scaffold
    fails alone on origin/main and passes here.

All four are one mechanism: an eager import was carrying side effects, and making
it lazy relocated them.

Recorded, not done — both on UN-4136 with mechanism and repro

  • Make a wholesale cloud-plugin import failure loud at boot. Today a broken wheel
    boots clean and surfaces per-request as No executor registered with name 'table'.
  • Fold the ~30 hand-copied registration guards into a shared test helper, so
    omission becomes impossible rather than merely discouraged.

Honest accounting of the eight rounds

Rounds 1–3 found real defects in the change. Rounds 4–8 were almost entirely my
own fixes being wrong
— twice removing a guard instead of narrowing its claim,
three times rewriting one comment into a new falsehood. A better first pass would
have needed half the rounds. The loop caught all of it, which is the point, but the
round count is not a boast.

@sonarqubecloud

Copy link
Copy Markdown

@ritwik-g
ritwik-g marked this pull request as ready for review September 22, 2026 04:25
@greptile-apps

greptile-apps Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

via Greptile

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the deployed executor paths retain registration before task consumption while file-processing imports remain isolated from the executor stack.

Summary

This PR removes expensive executor initialization from unrelated package imports while preserving explicit executor-worker startup behavior.

  • Resolves celery_app and LegacyExecutor lazily through module attribute hooks.
  • Introduces an idempotent, re-entrancy-aware register_all() registration path.
  • Registers bundled and entry-point executors explicitly when executor tasks load.
  • Relocates Celery result-log suppression to the task module used by deployed executor roles.
  • Adds isolated-process regression coverage for import boundaries and registration behavior.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    FP[File-processing worker] --> C[Import constants or ExecutorToolShim]
    C --> LP[Lightweight executor package initialization]
    LP -. does not load .-> EW[Executor worker stack]
    CE[Celery executor role] --> RT[workers/worker.py]
    PG[PG executor role] --> RT
    RT --> T[executor/tasks.py]
    T --> RA[register_all]
    RA --> LE[Import and register LegacyExecutor]
    RA --> EP[Discover cloud executor entry points]
    T --> X[Register execute_extraction task]
Loading

Reviews (1) · Last reviewed commit: "fix: give the test pointer a path, and S..."

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-api-deployment e2e 3 0 0 0 8.6
e2e-coowners e2e 1 0 0 0 1.4
e2e-etl e2e 1 0 0 0 14.5
e2e-login e2e 2 0 0 0 1.5
e2e-prompt-studio e2e 1 0 0 0 9.6
e2e-smoke e2e 2 0 0 0 1.5
e2e-workflow e2e 1 0 0 0 12.3
frontend unit 0 1 0 0 0.0
integration-backend integration 598 0 0 26 56.3
integration-connectors integration 1 0 0 7 7.9
integration-workers integration 159 5 0 1 43.1
ui e2e 0 1 0 0 0.0
unit-backend unit 1283 0 0 1 27.3
unit-connectors unit 63 0 0 0 6.3
unit-core unit 137 0 0 0 1.3
unit-platform-service unit 15 0 0 0 1.7
unit-rig unit 120 0 0 0 2.9
unit-runner unit 5 0 0 0 2.1
unit-sdk1 unit 580 0 0 0 20.2
unit-workers unit 1373 0 0 1 130.2
TOTAL 4345 7 0 36 348.7

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • api-deployment-run — covered by e2e-api-deployment
  • mcp-server-auth — covered by integration-backend
  • mcp-platform-auth — covered by integration-backend
  • platform-key-whoami — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • connector-register-test — covered by integration-backend
  • pipeline-etl-execute — covered by e2e-etl
  • usage-aggregate-read — covered by integration-backend
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

@ritwik-g

Copy link
Copy Markdown
Contributor Author

CI state — the test tiers ran, and under the scheduler that matters

Flagging this because the check rollup read None / SKIPPED for the test jobs
for most of this PR's life, and "green" could otherwise be misread as "tested".

They were gated on draft state, not broken. .github/workflows/ci-test.yaml:

if: needs.changes.outputs.relevant == 'true' && github.event.pull_request.draft == false

So the tiers were correctly skipped for the eight self-review rounds this PR spent
as a draft, and ran the moment it was marked ready:

job result
test (unit) pass — 4m53s
test (integration) pass — 4m3s
changes pass — path filter resolved relevant == true
Greptile Review pass — Confidence 5/5, no findings
SonarCloud pass — 0 new issues, 0 security hotspots
pre-commit.ci pass

Step-level: Validate test manifests → success, Run unit tier → success.

Why the unit tier is the right check for this change specifically. It runs
tox -e unit, which invokes the rig, which runs the unit-workers group
(workdir: workers) with -n <workers> and no --dist — i.e. pytest-xdist's
default per-test load scheduler.

That is precisely the scheduler this PR had to be hardened against. Making the
executor package import lazy moved when @ExecutorRegistry.register fires
relative to test fixtures, which breaks a module only when one of its nodes runs
without its file-mates — invisible to whole-file runs, and exactly what a per-test
scheduler produces. Four test files needed a guard as a result. CI running that
scheduler green is stronger evidence than the local suite.

Correcting one claim I made in an earlier round

Round 5's write-up narrowed an xdist claim on the grounds that "there is no
.github/workflows/ in this repo". That was wrong — there are seven workflow
files; I had checked with a *.yml glob and they are .yaml. CI does invoke the
rig, so the original, stronger claim held. The narrowed wording in
test_plugin_loader.py is still accurate, just less than it could have asserted.

What CI does not cover, unchanged from the PR body

  • The end-to-end 9.08s → 0.39s convergence — needs a staging deploy.
  • Cloud entry-point executors (table, agentic_table, line_item,
    single_pass_extraction, and four more) are not installed in CI or locally, so
    registration is exercised for legacy only. Their independence from the
    removed import side effect is established by a source sweep across all eight
    plugin packages, not by running them.

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.

2 participants