Skip to content

Under OS_EAGER_SCHEMAS=1, entering a deep spec leaf module first dies on a strict-object ↔ field.zod TDZ #6267

Description

@hotlong

Observation-class finding, measured while working #4415 on origin/main @ 8e2bbba24. Nothing a user hits today — filing so it is recorded rather than rediscovered.

What happens

Under OS_EAGER_SCHEMAS=1 (the documented lazySchema rollback switch, which gen:schema / check:authorable-surface set), importing a deep leaf schema module as the process entry point throws during module evaluation:

$ OS_EAGER_SCHEMAS=1 node --import tsx --input-type=module \
    -e "import './packages/spec/src/automation/control-flow.zod.ts'; console.log('ok');"

/packages/spec/src/shared/strict-object.ts:255
  DECLARATIONS.push({ options, shape });
  ^
ReferenceError: Cannot access 'DECLARATIONS' before initialization
    at strictObject (packages/spec/src/shared/strict-object.ts:255:3)
    at (packages/spec/src/data/field.zod.ts:113:52)
    at lazySchema (packages/spec/src/shared/lazy-schema.ts:23:12)
    at (packages/spec/src/data/field.zod.ts:113:35)
    at Object. (packages/spec/src/data/field.zod.ts:1076:1)

Why

DECLARATIONS is a module-level const declared after the strictObject function in strict-object.ts. Eager mode makes lazySchema(factory) run its factory at module-evaluation time, so when the import graph re-enters strictObject() while strict-object.ts is itself still evaluating, the const is still in its temporal dead zone.

Whether that re-entry happens is decided purely by which module the process enters first — it is an ordering property of the graph, not of any one file.

Why it is dormant

Every real entry point goes through a barrel (src/index.ts, src/automation/index.ts), which establishes an order in which strict-object.ts finishes evaluating before anything calls into it. Both eager-mode gates use those barrels, so CI never reaches the bad order. Verified green on the same commit:

$ OS_EAGER_SCHEMAS=1 node ... -e "await import('./packages/spec/src/automation/index.ts')"   # ok

Reproduces on origin/main, not caused by any in-flight branch

Measured by checking out the pristine origin/main copies of the two files I was editing and re-running — identical failure, so #4415's changes are not involved.

Possible fix

Hoist DECLARATIONS above strictObject (a one-line move), which removes the TDZ regardless of evaluation order. Worth a quick check for the same shape elsewhere in packages/spec/src/shared/alias-table-registry has a similar registry pattern.

No pm:queue: dormant, no user-visible symptom, and the fix is cheap enough to ride along with any future strict-object.ts work.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions