Skip to content

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

Closed

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.

Activity

  1. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): hold confirmed. The TDZ only fires when a process enters via a deep leaf module under the rollback switch — no tool does that today (gen:schema / check:authorable-surface enter via the full graph). Restart: any tool needing leaf-entry eager import, or a strict-object registration refactor touching DECLARATIONS.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions