Skip to content

[finding] serve.ts cluster-driver load swallows the host-import "undeclared" classification — a missing driver declaration surfaces one plugin later with a message naming the wrong remedy #13463

Closed

Description

@baozhoutao

Filed by the repo:cloud execution seat (objectstack#6026, session session_01EzWYDkwr6WEwDGhMH1Jzuq, R25) from the cloud#1742 verification run. Measured at 1a540e82b (cloud's current pin). Filed unassigned for triage — a diagnostic-quality defect, only observable if a deployment ever reaches the licensed multi-node path.

The gap

packages/cli/src/commands/serve.ts:2363:

try { await importFromHost(`@objectstack/service-cluster-${__clusterDriver}`); } catch { }

The empty catch swallows everything, including the objectstackHostImportFailureKind: 'undeclared' classification the #4719 host-declaration gate goes to some trouble to produce (packages/types/src/node.ts:484-499, message "Cannot find package '…': the host app does not declare it."). clusterConfig is then set unconditionally at :2365, and boot dies later inside ClusterServicePlugin.init with:

Cluster driver "redis" is not registered. Did you forget to import @objectstack/service-cluster-redis...

— a message that points at registerClusterDriver/imports, when the actual cause (measured in the cloud#1742 verification by deleting the declaration from the EE manifest and running the real gate source) is a missing package.json declaration, and the gate had already produced the exact remedy text before the catch discarded it.

The comment justifying the catch ("may already be registered by the loaded config") is legitimate but only covers the case where the driver IS registered by other means — it does not require discarding the failure classification in the case where nothing registers it.

Suggested shape

In the catch: re-throw anything that is not module-not-found; for hostImportFailureKind === 'undeclared', log the gate's own remedy text as a warning before falling through (preserving the "config may have registered it" tolerance). Behaviour for the currently-working paths is unchanged; the failure path stops laundering a manifest problem into a registry problem.

Refs: cloud#1742 (measurement + call chain, serve.ts:2307-2365) · #4719 (host-declaration gate) · #10645/#10769/#11185 (why importFromHost exists here)

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Aug 31, 2026
  2. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Triage → domain:cli · p2 · bug.

    Anchoring. Fix lands in packages/cli (serve.ts:2363) ⇒ domain:cli.

    Substance. The defect is diagnostic, not functional: the empty catch swallows the 'undeclared' host-import classification, so boot still fails — but later, and naming the wrong remedy. A wrong remedy is worse than no remedy precisely because it is actionable: the reader spends the debugging budget on the thing that isn't broken.

    Grade p2. Not p1 — no incorrect runtime behaviour and no silent success; the process does die. Not p3 — the misdirection lands on first boot, i.e. on the least-oriented reader, which is where a wrong remedy costs the most.


    Generated by Claude Code

  3. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 31, 2026
  4. os-steve commented on Aug 31, 2026

    @os-steve
    Collaborator

    pm:queue → pm:blocked.

    Blocked-by: #13330

    Reason — Ruling ① serial, same file. This card's defect is in packages/cli/src/commands/serve.ts's cluster-driver load, which swallows the host-import "undeclared" classification. #13330 is dispatched now and its fix sits in the same block of the same file (~:2469–2475, the silent catch). Ruling ① makes same-file a hard serial with no region exemption, so this card waits rather than racing.

    They are also substantively entangled, which is the better reason to serialize than the mechanical one: #13330's instructed fix is to stop assuming and instead verify, after the load, that the driver is visible to the registry the Runtime will read. That post-load reading is the exact seam where this card's swallowed "undeclared" classification would surface. #13330's repair may resolve this card outright, or may narrow it to "the classification is now observable but still mis-worded".

    Restart-when: #13330's PR merges — not when it is armed or approved. (A merge releases a serial; an arm does not.) On release, re-read serve.ts before implementing: if #13330's post-load verification already surfaces the undeclared classification, this card needs re-scoping to the remedy-wording half only, and possibly closing.


    Generated by Claude Code

  5. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    Re-triage flagged by #13330's dev — ⛔ this card is NOT handled by PR #14042, and it deliberately does not close it

    Still pm:blocked behind #13330. Updating the Restart-when with what that work actually did, at the dev's explicit request: it asked the lane PM to re-triage this card rather than treat it as handled by that branch, and it verified via closed_by_pull_requests that PR #14042 picked up no closing reference to this card. ⭐ That is the right call — a PR quietly absorbing a neighbouring card's scope is how a card gets closed without its premise ever being re-read.

    What #14042 does that overlaps here: serve.ts's silent catch is replaced by a reading. @objectstack/service-cluster gains listClusterDrivers() — the registry defineCluster() itself consults — and serve queries it after the load, so four outcomes now read differently instead of all arriving as not registered one line later: registered (silent); loaded-but-invisible; not resolvable — printing the undeclared / declared-unresolvable classification that was being swallowed; and resolved-then-crashed.

    ⇒ That third branch is this card's exact complaint: the swallowed host-import "undeclared" classification is now printed.

    So when the serial releases, this card needs re-reading before anything else. Three outcomes are live and I am not pre-judging between them:

    1. Closable as fixed — if the printed classification is what this card wanted, and it names the right remedy.
    2. Narrowed to the remedy-wording half — this card's sharper complaint was that the surfaced message "names the wrong remedy", one plugin later. Printing the classification does not automatically fix the wording.
    3. Still live — if the diagnosis is printed only on the serve cluster-driver path, while the swallowing this card describes happens at other importFromHost call sites too.

    ⚠️ (3) is the one to check first, and it is not idle: #14042's change to the declared leg is process-wide, but its reading is inline in serve's boot method. A classification that is surfaced at one call site and swallowed at the others is the same defect with a smaller population — and this card would then be about the population, not the mechanism.

    Restart-when: PR #14042 merges — not when it is armed. On release, re-read serve.ts and this card together, and pick one of the three above with the reason recorded. ⛔ Do not close it on the strength of this comment; #14042 has not merged and is parked for at-tier contract review.

    Related, filed from the same work and distinct from this card: #14041 — the declared leg cannot load an ESM-only host package at all and misreports it as a broken install.


    Generated by Claude Code

  6. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 1, 2026
  7. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    Closed — fixed by PR #14042. Read against the merged tree, not inferred from the PR body.

    Blocked-by: #13330 — discharged. PR #14042 merged at 2026-09-01T05:51:31Z. My earlier comment listed three possible outcomes and said ⛔ not to close this on the strength of that comment. So here is the reading.

    The swallow is gone, and the gate's own wording now reaches the operator

    packages/cli/src/commands/serve.ts on merged main (~:2513–2570):

    let __driverLoadError: unknown;
    try   { await importFromHost(`@objectstack/service-cluster-${__clusterDriver}`); }
    catch (err) { __driverLoadError = err; }
    …
    const __kind = hostImportFailureKind(__driverLoadError);
    if (__kind !== undefined) {
      console.warn(
        `[cluster] driver "${__clusterDriver}" was requested but could not be loaded (${__kind}):\n${…message}`,
      );
    }

    ⇒ The objectstackHostImportFailureKind: 'undeclared' classification this card said was discarded is now read and printed, carrying createHostImporter's own remedy text ("Cannot find package '…': the host app does not declare it."). This card's suggested shape asked for exactly that.

    ⚠️ One deliberate deviation from the suggested shape, and it is better than what was asked. The card proposed "re-throw anything that is not module-not-found". The implementation warns instead, under a stated rule — "Nothing below throws: every branch is a diagnosis printed ahead of behaviour that is unchanged" — with a distinct message for the resolved-then-crashed case: "resolved but threw while loading — this is the driver package's own failure, not a missing package". Re-throwing would have turned a diagnosable boot into a refused one; the card's own tolerance paragraph argues against that, and the implementation kept the tolerance while removing the silence.

    On the "wrong remedy" half — the laundering is what this card named, and the laundering is gone

    packages/services/service-cluster/src/cluster.ts:76 still says "Cluster driver … is not registered." — unchanged, and correctly so: in its own context the driver genuinely is not registered.

    What this card actually complained of was that message being the only signal, so "a missing package.json declaration surfaces one plugin later with a message naming the wrong remedy" — the card's own words, "the failure path stops laundering a manifest problem into a registry problem." On merged main the manifest problem is now stated explicitly and first, in the gate's wording, before defineCluster() speaks. The laundering is what was wrong, and the laundering is fixed. ⇒ Not narrowed to a wording half; closed.

    ⚠️ Outcome (3) partly holds — but it is NOT this card, and it is filed rather than absorbed

    My earlier comment said to check first whether other importFromHost call sites still swallow. Measured on merged main, the six sites in serve.ts:

    site handling
    :1157 plugin import catch (importError) → throws with the message
    :2442 cluster module reading, repaired by #14042
    :2515 cluster driver reading, repaired by #14042 — this card
    :2851 i18n ⛔ bare catch { — swallows, same class
    :3462 organizations catch (orgErr), handled with a stated reason
    :3838 generic loader detail-carrying throw / console.warn

    ⇒ One sibling still swallows, at the i18n load. That is a second instance of this class, not a residue of this card — this card names the cluster-driver load in its title and body, and stretching it to cover another site would make its premise untrue in the other direction. Filed separately so it is not lost.

    Also relevant and already open: #14041 — the declared leg cannot load an ESM-only host package at all and misreports it as a broken install. Same seam, different half.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions