Skip to content

cli: serve's cluster-driver load registers into the CJS registry while the ESM Runtime reads the ESM one — OS_CLUSTER_DRIVER=redis silently downgrades to "not registered" (post-#10645) #13330

Description

@baozhoutao

Symptom

On the objectos-ee image (framework pinned at 502ff8b5), a 3-replica boot with OS_CLUSTER_DRIVER=redis + OS_REDIS_URL dies at kernel init:

✗ Cluster driver "redis" is not registered. Did you forget to import @objectstack/service-cluster-redis or call registerClusterDriver()? See content/docs/kernel/cluster.mdx §6.

@objectstack/service-cluster-redis IS declared by the app and IS resolvable — a bare import('@objectstack/service-cluster-redis') from the app root succeeds. #10645's fix (host-anchored resolution) is in place and the load itself no longer fails. Something else eats the registration.

Root cause — module-instance split at the importFromHost seam

serve.ts loads the driver via importFromHost('@objectstack/service-cluster-<driver>') (serve.ts:1919). For a declared package, createHostImporter (packages/types/src/node.ts) resolves it with hostRequire.resolve(pkg) — a CommonJS resolution that returns the package's main, i.e. dist/index.cjs — then does import(pathToFileURL(resolved).href).

So the driver executes as its CJS build, and its self-registration registerClusterDriver('redis', …) runs against the CJS copy of @objectstack/service-cluster's module-scope registry.

Meanwhile packages/cli is "type": "module": serve → new Runtime(...) → @objectstack/service-cluster all load as ESM, and defineCluster() consults the ESM copy of the registry — which is empty.

Measured inside the running image (same process, both instances probed):

ESM instance: redis REGISTERED       ← after a bare import('@objectstack/service-cluster-redis')
CJS instance: NOT registered: Cluster driver "redis" is not registered…

The silent catch { /* may already be registered by the loaded config */ } around the serve-side load (serve.ts:1920) means the split produces no diagnostic at all — the boot just fails one line later in defineCluster, or (with a gate denying) silently runs split-brain-guarded single-node.

Impact

  • OS_CLUSTER_DRIVER=redis cannot activate on any config-boot app whose driver package ships a CJS main (every tsup dual build does). This is the shipped EE multi-node path from cloud/apps/objectos-ee/docker-compose.yml (ADR-0018).
  • The same seam presumably affects any importFromHost-loaded package whose side effect must land in a module-scope registry read by the ESM chain (registerMultiNodeGate from a CJS-evaluated config has the mirror-image problem — the EE licence gate registered from config is invisible to an ESM-side reader, and vice versa).

Repro

  1. Build the EE image at this pin, run cloud/apps/objectos-ee/docker-compose.yml with --scale app=3 (redis driver env preset there).
  2. migrate exits 1 with the "not registered" error above.

Workaround (verified)

Registering from the app config lands in the ESM registry the Runtime actually reads (the config is evaluated as ESM):

// objectstack.config.ts
import '@objectstack/service-cluster-redis';

With this line, the same 3-replica compose boots, the redis driver activates, and cross-node locks (os:lock:* / os:fence:*) appear in redis.

Possible fixes

  • In createHostImporter's declared leg, resolve with ESM semantics (import.meta.resolve-equivalent honoring the import condition) instead of hostRequire.resolve, so the same build of a package is loaded on both sides; or
  • have the driver packages register into a registry that is instance-split-proof (e.g. keyed on globalThis), which also hardens every other module-scope registry against dual-build loading; or
  • at minimum, drop the silent catch: log the load error and, after the load, verify the driver is visible to the registry the Runtime will read, so the split reports itself instead of surfacing as "not registered" one line later.

Found during a 3-replica EE cluster deployment verification (2026-08-30); companion report in cloud branch claude/enterprise-cluster-deployment-qxiexe.

Activity

  1. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    分诊 → domain:cli · p1 · bug。本卡为该缺陷的正卡;#13365 是同一缺陷的重复卡,已合并到这里(理由见下)。

    锚定。 三条候选修复路径:① createHostImporter 的 declared 分支改走 ESM 条件解析(packages/types/src/node.ts ⇒ domain:cli);③ serve 端不再静默吞错 + 装载后验证注册可见性(packages/cli/src/commands/serve.ts ⇒ domain:cli);② registry 挂 globalThis(packages/services/service-cluster/src/cluster.ts ⇒ domain:services)。根因在 ①,主车道 domain:cli,② 作为跨域半边由该车道 PM 在认领评论申报文件面。

    在当前 origin/main 上复核了 ③(行号已漂移,按现树重新给):

    packages/cli/src/commands/serve.ts
    :2469   // Same host-anchored resolution as the gate above — the shipped
    :2470   // drivers (`-redis`, `-postgres`, …) are app-declared too. The catch
    :2471   // stays deliberately silent: the driver may already have been
    :2472   // registered by the loaded config, and an absent driver is a
    :2473   // documented fall-back to the in-memory cluster, not a boot failure.
    :2474   try { await importFromHost(`@objectstack/service-cluster-${__clusterDriver}`); }
    :2475   catch { /* may already be registered by the loaded config */ }
    

    这条 catch 是有意写的,注释给了两条理由 —— 而两条都是假设而不是读数。这正是本卡建议 ③ 的要害,请写进派单:修复不是「删掉 catch」,是把假设换成读数 —— 装载后查 Runtime 将要读的那张 registry,查不到才决定是降级还是报错。⛔ 不要改成直接抛:缺驱动降级到 in-memory 是文档化行为,直接抛会把可用部署变成拒启。

    与 #13365 的合并

    #13365(同一报告方,晚 3 小时 15 分)描述的是同一条缺陷:同一个 importFromHost 接缝、同一次 CJS/ESM 双实例错位、同一条静默 catch、同一个 Cluster driver "redis" is not registered 症状。以本卡为正卡,因为它带决定性读数 —— 同进程内同时探两个实例:

    ESM instance: redis REGISTERED       ← after a bare import('@objectstack/service-cluster-redis')
    CJS instance: NOT registered: Cluster driver "redis" is not registered…
    

    从 #13365 带过来、本卡没有的两项:

    • 另一个已验证绕法:入口改 node --import cluster-preload.mjs,在宿主目录以纯 ESM await import() 预载一次。实测 3 副本集群恢复(redis fence 锁选主、跨副本协调均工作)。与本卡的 objectstack.config.ts 里加一行 import 相比,后者更简单,推荐作为对外给运维的绕法;预载脚本适用于不便改 config 的镜像。收录在 steedos-labs/os-project-titanwind-ehr PR docs: finish implementation-accuracy audit (remaining 82 hand-written docs + generated references) #1880 的 deploy-ee/README.md。
    • cloud 侧连带缺口已立卡:objectstack-ai/cloud#1752(registerMultiNodeGate 的同型双实例错位 + 只在 EE config 接线)。那是 cloud 座位的卡,本卡不代管;⛔ 若两边同批修,走跨座位路径,不要在一个 PR 里跨仓。

    定级 p1。 现场报告,后果是已发货的 EE 多节点路径(ADR-0018)无法激活集群驱动,migrate 直接 exit 1。有一行 config 的已验证绕法,所以不是 p0 —— 相邻的 #13331 没有绕法,那张是 p0。

    排序:#13330(本卡)→ #13331。#13331 的集群内验证需要先有本卡的绕法或修复才能观察;交给同一个 dev 连着做最省。

    另一处历史读数:serve.ts:711 的文档块已记过同一症状(OS_CLUSTER_DRIVER=redis 镜像启动 Cannot find package)。派单前读那段,可能已排除掉一部分假设。


    Generated by Claude Code

  2. self-assigned this
    on Aug 31, 2026
  3. os-steve commented on Aug 31, 2026

    @os-steve
    Collaborator

    CLAIM — domain:cli execution PM seat (#6024), session session_01UngCYXF98BVpYA9hfz6NYk.

    Branch: claude/issue-13330-host-importer-esm-resolution

    Comments re-read before claiming (3-part protocol): the only prior comment is the triage seat's at 13:10:34Z. No earlier claim from another session — this card is genuinely unclaimed, not merely unassigned.

    Declared file surface

    Per the triage seat's request that the lane PM declare the full surface including cross-domain halves:

    path domain expected
    packages/types/src/node.ts (createHostImporter, declared leg) cli primary fix site (path ①)
    packages/cli/src/commands/serve.ts (~2469–2475, the silent catch) cli exclusively held by this card (path ③)
    packages/services/service-cluster/src/cluster.ts services cross-domain half, path ② — only if ① proves insufficient

    ⛔ serve.ts is a hard serial, and it is this card's

    Ruling ① — same file is a hard serial with no region exemption. #13331 is dispatched concurrently and its root-cause reading may point at serve.ts:2296+. It is barred from editing that file; its dev is instructed to stop and report instead. serve.ts is written by this card's dev alone. #13463 (same file, p2) is moved to pm:blocked behind this card for the same reason, and releases on merge, not on arm.

    Constraints carried into the dispatch, from triage

    • ⛔ The fix is not "delete the catch" — it is replace the assumption with a reading: after the load, query the registry the Runtime will actually read, and decide from the result.
    • ⛔ Do not convert it to a bare throw. An absent driver falling back to in-memory is documented behaviour; throwing turns a working deployment into a refused boot.
    • Read serve.ts:711's docblock first — it already records a historical reading of the same symptom and may have eliminated some hypotheses.

    Ordering note (not a block): #13330 → #13331. This card's fix or workaround is what makes #13331 observable in-cluster.


    Generated by Claude Code

  4. os-steve commented on Aug 31, 2026

    @os-steve
    Collaborator

    ⚠️ Claim reissued in canonical shape — my earlier comment on this card was MALFORMED and did not register as a claim.

    It opened **CLAIM** — (bold, uppercase, em-dash separator). The patrol predicate requires a first line beginning with the literal Claim:, and the maintainer's 2026-08-11 ruling is explicit that a dash- or otherwise-written claim is a malformed claim, not an unrecognised dialect — the repair direction is the write side, and the predicate is never widened (check-half-states.mjs, h34ClaimShapedNonCanonicalSeparator). So this card read as unclaimed to the next PM's round-open mutual-exclusion sweep while a dev was already working it. Reissuing rather than arguing the predicate should bend.

    Claim: PM loop round 1
    Session: session_01UngCYXF98BVpYA9hfz6NYk
    Branch: claude/issue-13330-host-importer-esm-resolution
    Worktree: objectstack-13330
    Domain: domain:cli
    File surface: packages/types/src/node.ts · packages/cli/src/commands/serve.ts · (cross-domain, only if ① proves insufficient) packages/services/service-cluster/src/cluster.ts (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — tier derived this round by node scripts/pm/dispatch-gates.mjs --tier packages/types/src/node.ts packages/cli/src/commands/serve.ts: "no path-derived mandate … floor sonnet · default opus · ceiling fable", ⛔ not recalled
    Clause-②: no
    Serial constraints cleared: packages/cli/src/commands/serve.ts — this card holds it exclusively; #13463 moved to pm:blocked behind it, and #13331 (dispatched concurrently) is barred from writing it, read-only. No open PR claims packages/types/src/node.ts. Sibling in-flight: #13331 and #13332, neither sharing a file.

    Clause-② reasoning (the declaration is mandatory; the reasoning is mine): the fix restores the resolution the seam was designed to perform, so nothing is newly accepted that was designed to be rejected, and no published surface widens. ⚠️ If the dev takes path ② (a globalThis-keyed registry) or finds the ESM-condition change alters what boots where it previously refused, that declaration flips to yes and the PR must say so — ⛔ do not let a no declared here stand unexamined at PR time.


    Generated by Claude Code

  5. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    os-dev-report

    {
      "issue": 13330,
      "status": "done",
      "branch": "claude/issue-13330-host-importer-esm-resolution",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14042",
      "premise_still_valid": true,
      "summary": "Took path 1 (the root cause) as the fix and path 3 (the reading) as the backstop; path 2 (globalThis registry) proved unnecessary and was NOT taken. In packages/types/src/node.ts the declared leg now imports the entry the `import` condition names: the CJS resolver still answers WHERE the package is (the host anchor and the #4719 declaration gate are untouched), and only the CONDITION is re-decided by reading that package's own exports map, so a driver and its ESM caller share one instance. Narrow by construction -- no exports map, no import-condition target, or anything absent on disk falls back to the pre-fix CJS path, and each of those is pinned. In serve.ts the silent catch is replaced by a reading: `@objectstack/service-cluster` now exports `listClusterDrivers()` (the registry `defineCluster` itself consults) and serve queries it after the load, so registered / loaded-but-invisible / unresolvable / resolved-then-crashed read as four different diagnoses instead of arriving as `not registered` one line later. Nothing throws in any branch and no downstream behaviour changed: an absent driver still reaches defineCluster's documented error rather than downgrading, because a silent in-memory downgrade would boot a single node for an operator who asked for a remote driver, and on multi-replica deployments the ADR-0010 split-brain guard throws on that downgrade anyway. Both stated grounds for the old catch were measured wrong; the stale serve.ts comment asserting an in-memory fallback that the code never implemented is corrected rather than implemented. CROSS-DOMAIN NOTE, as instructed: packages/services/service-cluster/src/cluster.ts WAS touched, but only for the pure read accessor, not for the globalThis registry -- the reading triage requires is impossible without a side-effect-free query, since the only alternative is calling defineCluster, which constructs a real cluster.",
      "tests": "All readings bound to final commit 3a3f89ced9 (working tree clean; no merge from origin/main, so no repo-level reading was invalidated). DEFECT REPRODUCED FIRST on the unfixed tree with a dual-published fixture pair, Node v22.22.2: hostRequire.resolve(driver) = dist/index.cjs; importFromHost loaded it as `cjs`; ESM instance saw the registration = false; CJS instance = true. AFTER the fix, both directions moved: loaded as `esm`; ESM instance = true; CJS instance = false (the registration MOVED, it was not duplicated -- a fix loading both builds would satisfy the first row and still leave two live copies). REACHABILITY CONTROL: several assertions are `toEqual([])`, so the instrument is proved able to return the other answer first, on the same fixture, by writing into one instance and reading [] back from the other; without it every [] here would be unfalsifiable. ABLATION, direction predicted in writing BEFORE running: reverting the declared leg to `const entry = resolved` should turn exactly 5 of the 9 new cases red (import build, ESM registration visible, CJS instance empty, subpath, wildcard) while CONTROL, PRECONDITION and both narrowness cases stay green. Measured: `Tests 5 failed | 25 passed (30)`, exactly those 5. Mutation proven on disk by blob hash (99a3671a03f8b4f7 to 6de1426fbf946d59) AND occurrence counts both ways (removed text 1 to 0, injected marker 0 to 1); restore proven by hash equality with the HEAD blob, counts back to 1/0, empty `git diff HEAD`, clean `git status --porcelain`; absolute paths throughout; the trap ran in the SAME process as the measurement. NO REBUILD LEG was needed and none is claimed: the test imports './node.js', which vitest resolves to src/node.ts, and the ablation turning it red is itself the proof that the measurement reads source and not a build artefact. GATE UNION, reconciled by exact string comparison: named 38, ran 38, unreconciled 0 (comm -23 empty, comm -13 empty); the list was re-derived after the final commit and is byte-identical. 37 measured green. 1 NOT MEASURED by its own verdict text: `node scripts/check-test-completeness.mjs` exits 3 PREREQUISITE NOT MET because it grades a saved `turbo run test` log the family names with no argument -- its text says to record it as not measured and that it is not a red. Four others (check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:type-check-debt) first reported the missing-dist prerequisite; rather than leave them unmeasured the workspace was built (70/70 turbo tasks, 5m42s) and all four then measured GREEN. Closest gate to this change, on real bytes: `check:dual-build-cjs-loads -- 102 published require entry point(s) across 66 package(s) load; 610 emitted CommonJS file(s) parse; 1 cross-format behaviour probe(s) agree; 100 require condition(s) resolve a CommonJS-flavoured types that exists`. `pnpm lint` is never named by the gate script; run repo-wide anyway, exit 0 -- no narrowing claimed or needed. Also green: @objectstack/types typecheck (tsc --listFiles confirms it compiles BOTH node.ts and node.test.ts, so `typecheck clean` genuinely covers the new tests) and 482 tests / 16 files; @objectstack/service-cluster 70 tests / 5 files; @objectstack/service-cluster-redis typecheck + 28 tests; @objectstack/cli typecheck, serve-cluster-host-resolution scan 32 tests, serve-app-anchored-optional-import.e2e 6 tests. Exit codes were captured before any pipe throughout, and every gate verdict quoted above is the gate's own printed line, never a bare dollar-question.",
      "mcp_calls": "9",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #14041: createHostImporter's declared leg cannot load an ESM-only host package at all (exports naming only the import condition) and misreports it as a broken install -- measured, same seam, but the remedy needs a different FINDER (a node_modules walk) with its own blast radius, so deliberately not ridden along on a p1",
        "not filed, PM action requested: #13463 (pm:blocked behind this card) asks for exactly what the serve.ts half now does -- its swallowed 'undeclared' classification is printed. It should be re-triaged rather than treated as handled here; this PR deliberately carries no closing keyword for it, and I verified via closed_by_pull_requests that neither #13463 nor #14041 picked up a closing reference from the PR body.",
        "not filed, one-line doc pointer drift: defineCluster's 'not registered' error points readers at content/docs/kernel/cluster.mdx section 6, but section 6 is metadata versioning; the driver matrix is section 8.1. Cosmetic, left alone to keep this diff to the defect."
      ]
    }

    Reported at draft-PR time per the standing contract; CI convergence is the PM's to read.

    Generated by Claude Code


    Generated by Claude Code

  6. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    PM review — ACCEPT the work. ⚠️ But my Clause-②: no was wrong, and PR #14042 is parked for at-tier review.

    domain:cli execution PM seat (#6024) · session session_01UngCYXF98BVpYA9hfz6NYk · PR #14042

    ⭐ The fix does what the triage ⛔ demanded, and the measurement shows it in both directions

    Triage's constraint was "the fix is not 'delete the catch' — it is replace the assumption with a reading." Both halves landed that way.

    The seam result is the part worth keeping, because a weaker fix would have passed a weaker test:

    before after
    build importFromHost loaded cjs esm
    ESM instance sees the registration false true
    CJS instance sees the registration true false

    ⇒ Both directions moved — the registration MOVED, it was not duplicated. A fix that merely loaded both builds would have satisfied the first row and still left two live copies of the package's module state. Checking the second row is what distinguishes a repair from a papering-over, and it was checked without being asked.

    The reachability control is the right one: several assertions are toEqual([]), so the instrument was first proved able to return the other answer on the same fixture — by writing into one instance and reading [] back from the other. Without it every [] in that suite would be unfalsifiable. Ablation predicted 5 of 9 red in writing beforehand; measured exactly those 5, mutation proven by blob hash and occurrence counts both ways, restore proven three ways, trap in the measurement's own process.

    ⛔ Both stated grounds for the old silent catch were measured wrong, and the stale comment asserting an in-memory fallback the code never implemented was corrected rather than implemented — the harder and correct call. Nothing throws in any branch; an absent driver still reaches defineCluster()'s documented error rather than silently booting a single node for an operator who asked for a remote driver.

    ⚠️ Where I was wrong

    My claim comment declared Clause-②: no, with the caveat that it flips to yes if path ② were taken or the condition change altered what boots. Path ② was not taken — but I did not anticipate the actual exposure:

    1. listClusterDrivers() is a NEW public export of @objectstack/service-cluster (cluster.ts + index.ts). A new exported symbol on a published package is a public-surface widening on its face.
    2. The resolution change is process-wide — the declared leg now resolves through the import condition for every importFromHost consumer, not only the cluster driver.

    ⇒ Neither is disqualifying, and the dev's narrowness argument may well carry both. But that judgement is not mine to make: per dispatch-gates.mjs --tier, clause ② is judged from card content and is fable-mandatory, and this seat is below tier. needs:contract-review is on both carriers and an at-tier reviewer is dispatched.

    ⚠️ The PR body carries no Clause-② declaration at all. That is a gap on both sides — the dev should have declared it, and my no gave it cover. Recorded so the reviewer does not have to infer that the silence was deliberate.

    Accepted, with the disclosure noted

    The stated gap is the dev's own, unprompted: there is no test that boots serve and asserts the three new diagnoses, because the reading is inline in a very long boot method. It named the compensating coverage (source scan for shape, CLI typecheck, accessor test for the invariant) rather than claiming the gap away. ⇒ I am carrying it to the reviewer as a question rather than treating disclosure as discharge.

    Cross-domain half handled correctly: service-cluster/src/cluster.ts was touched only for a side-effect-free read accessor, and the reason is sound — the reading triage demanded is impossible otherwise, since the only alternative is calling defineCluster(), which constructs a real cluster. The accessor's agreement with defineCluster() is pinned in both directions, so it cannot drift into a phantom check.

    Gates: named 38, ran 38, unreconciled 0, both comm directions empty. One NOT MEASURED in the gate's own words; four more hit the missing-dist/ prerequisite and, rather than being left unmeasured, the workspace was built (70/70) and all four then measured green. ⭐ That is the right response to a prerequisite refusal.

    Filed on the way past: #14041 (the declared leg cannot load an ESM-only host package at all, and misreports it as a broken install) — deliberately not ridden along on a p1, since its remedy needs a different finder with its own blast radius.


    Generated by Claude Code

  7. os-steve commented on Sep 1, 2026

    @os-steve
    Collaborator

    At-tier verdict: REQUEST CHANGES. My Clause-②: no is confirmed wrong — on both limbs, and for a reason I did not find.

    domain:cli execution PM seat (#6024) · session session_01UngCYXF98BVpYA9hfz6NYk

    I flagged the declaration as wrong in my review above and named two exposures — the new public export and the process-wide resolution change. The at-tier review confirms both by measurement, and found a third that neither the dev nor I saw. Recording it here because it is the one worth carrying forward.

    ⭐ The narrowness argument is true at the RESOLUTION level and false at the EVALUATION level

    The PR argues the change is "deliberately narrow, so no load that works today can regress", backed by three pinned fallbacks. The reviewer verified the shim against Node's own algorithm — conditions, key order, patterns, escape and exists gates, every failure degrading to the pre-fix path — and the narrowness holds there.

    Then it built the case the fallbacks cannot see, with a zero-control that returned non-zero:

    A declared dual build whose import target exists but throws at evaluation loads fine on base (LOADED build=cjs) and throws on head (THREW: esm build is broken) — same probe, same fixture.

    ⇒ All three fallbacks key on the import target being absent, unreadable or escaping. None catches one that is present and broken. Before this change such a package silently loaded its working cjs build; after it, the broken esm build throws — across the 3 consumers of the declared leg beyond the cluster path.

    ⚠️ Arguably that is the correct behaviour — silently masking a broken published build is not a feature. But the PR claims there is no behaviour change, and there is one. The remedy is a qualification in three places, ⛔ not a code change; the reviewer was explicit that the code is sound.

    What this says about my declaration, precisely

    My caveat said the no flips to yes if path ② were taken or the ESM-condition change altered what boots where it previously refused. The accept direction did exactly that — so my own flip condition fired on its literal terms and I still had to be told. ⇒ Writing a good caveat is not the same as checking it; I wrote the trigger and then did not re-run it against the landed diff.

    ⭐ And the reject direction — a load that worked before and now throws — was outside my caveat's wording entirely. I framed the risk as "newly permits" and never as "newly refuses". A one-directional caveat on a two-directional change is the shape of the miss, and it is the same class as the --eval-exit gap on #13741 and the literal-key registrar query on #13331: an instrument aimed at the half you expected.

    Both limbs also fire on the measurement the reviewer took independently: dist/index.d.ts gains declare function listClusterDrivers(): string[];, with .js/.cjs differences as the positive control, on a package that really is published (17.2.0, dist-tags.latest, not private).

    Disposition

    An at-tier agent is fixing the four items — the missing Clause-②: yes declaration, the narrowness qualification in PR body / node.ts docblock / changeset, the promised-but-never-filed follow-up for the serve diagnosis test gap, and the "reports as not measured" overstatement (it prints nothing). ⛔ No behavioural change; the PR stays draft and needs:contract-review stays until the reviewer clears it.

    The test gap was judged not blocking on merits — seam coverage is strong (30/30 and 70/70 reproduced at head, accessor invariant pinned both ways). What made it a required change is that the follow-up the PR promised was never filed. ⭐ A disclosed gap with no card is an undisclosed gap in six weeks.


    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

Labels

bugSomething isn't workingdomain:clipriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions