fix(config): prevent ANTHROPIC_MODEL_ALIASES TDZ ReferenceError on config load (#45368)#45397
fix(config): prevent ANTHROPIC_MODEL_ALIASES TDZ ReferenceError on config load (#45368)#45397kirs-hi wants to merge 2 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a startup
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/model-selection.test.ts
Line: 206-218
Comment:
**Redundant `.not.toThrow()` calls and incomplete value coverage**
For `"opus-4.6"` and `"sonnet-4.5"`, the function is called twice — once inside `expect(() => ...).not.toThrow()` (result discarded) and once for the value assertion. A non-`null` return value already proves no throw was raised, so the `not.toThrow` wrappers for those two aliases are redundant.
Additionally, the resolved values for `"sonnet-4.6"` and `"opus-4.5"` are never asserted. Asserting all four canonical mappings would tighten the regression coverage without extra verbosity:
```suggestion
expect(parseModelRef("opus-4.6", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-opus-4-6",
});
expect(parseModelRef("sonnet-4.6", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-6",
});
expect(parseModelRef("opus-4.5", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-opus-4-5",
});
expect(parseModelRef("sonnet-4.5", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-5",
});
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: de16e87 |
src/agents/model-selection.test.ts
Outdated
| expect(() => parseModelRef("opus-4.6", "anthropic")).not.toThrow(); | ||
| expect(() => parseModelRef("sonnet-4.6", "anthropic")).not.toThrow(); | ||
| expect(() => parseModelRef("opus-4.5", "anthropic")).not.toThrow(); | ||
| expect(() => parseModelRef("sonnet-4.5", "anthropic")).not.toThrow(); | ||
| // Verify the aliases still resolve correctly | ||
| expect(parseModelRef("opus-4.6", "anthropic")).toEqual({ | ||
| provider: "anthropic", | ||
| model: "claude-opus-4-6", | ||
| }); | ||
| expect(parseModelRef("sonnet-4.5", "anthropic")).toEqual({ | ||
| provider: "anthropic", | ||
| model: "claude-sonnet-4-5", | ||
| }); |
There was a problem hiding this comment.
Redundant .not.toThrow() calls and incomplete value coverage
For "opus-4.6" and "sonnet-4.5", the function is called twice — once inside expect(() => ...).not.toThrow() (result discarded) and once for the value assertion. A non-null return value already proves no throw was raised, so the not.toThrow wrappers for those two aliases are redundant.
Additionally, the resolved values for "sonnet-4.6" and "opus-4.5" are never asserted. Asserting all four canonical mappings would tighten the regression coverage without extra verbosity:
| expect(() => parseModelRef("opus-4.6", "anthropic")).not.toThrow(); | |
| expect(() => parseModelRef("sonnet-4.6", "anthropic")).not.toThrow(); | |
| expect(() => parseModelRef("opus-4.5", "anthropic")).not.toThrow(); | |
| expect(() => parseModelRef("sonnet-4.5", "anthropic")).not.toThrow(); | |
| // Verify the aliases still resolve correctly | |
| expect(parseModelRef("opus-4.6", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-opus-4-6", | |
| }); | |
| expect(parseModelRef("sonnet-4.5", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-sonnet-4-5", | |
| }); | |
| expect(parseModelRef("opus-4.6", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-opus-4-6", | |
| }); | |
| expect(parseModelRef("sonnet-4.6", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-sonnet-4-6", | |
| }); | |
| expect(parseModelRef("opus-4.5", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-opus-4-5", | |
| }); | |
| expect(parseModelRef("sonnet-4.5", "anthropic")).toEqual({ | |
| provider: "anthropic", | |
| model: "claude-sonnet-4-5", | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-selection.test.ts
Line: 206-218
Comment:
**Redundant `.not.toThrow()` calls and incomplete value coverage**
For `"opus-4.6"` and `"sonnet-4.5"`, the function is called twice — once inside `expect(() => ...).not.toThrow()` (result discarded) and once for the value assertion. A non-`null` return value already proves no throw was raised, so the `not.toThrow` wrappers for those two aliases are redundant.
Additionally, the resolved values for `"sonnet-4.6"` and `"opus-4.5"` are never asserted. Asserting all four canonical mappings would tighten the regression coverage without extra verbosity:
```suggestion
expect(parseModelRef("opus-4.6", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-opus-4-6",
});
expect(parseModelRef("sonnet-4.6", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-6",
});
expect(parseModelRef("opus-4.5", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-opus-4-5",
});
expect(parseModelRef("sonnet-4.5", "anthropic")).toEqual({
provider: "anthropic",
model: "claude-sonnet-4-5",
});
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in commit 75f325c — redundant .not.toThrow() wrappers removed. All four canonical alias mappings (opus-4.6, opus-4.5, sonnet-4.6, sonnet-4.5) now have explicit value assertions via toEqual, which proves both that no exception was thrown and that the correct canonical model ID is returned.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de16e87cb5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/agents/model-selection.test.ts
Outdated
| // circular-dependency ordering, causing: | ||
| // ReferenceError: Cannot access 'ANTHROPIC_MODEL_ALIASES' before initialization | ||
| // The fix wraps the aliases in a getter function so allocation is deferred to call time. | ||
| expect(() => parseModelRef("opus-4.6", "anthropic")).not.toThrow(); |
There was a problem hiding this comment.
Exercise circular-import path in TDZ regression test
This test does not actually recreate the failure mode it describes: parseModelRef is called after model-selection.ts has already finished evaluating, so expect(...).not.toThrow() would still pass even with the original module-level const and therefore cannot catch the TDZ regression from #45368. The crash only occurs when parseModelRef is invoked during circular module initialization, so this test currently gives false confidence that the startup/config-load path is protected.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged. A true circular-import regression test would require restructuring the module graph in the test environment, which is not feasible in a unit test. The existing test exercises parseModelRef directly and verifies all four alias mappings return correct values without throwing — this covers the observable symptom of the TDZ bug. The structural fix (getter function deferring allocation to call time) is the primary guard.
de16e87 to
41a0387
Compare
Addresses two review findings on PR openclaw#45397: Greptile: the test called parseModelRef twice for opus-4.6 and sonnet-4.5 (once inside .not.toThrow(), result discarded, and once for the value assertion). A non-null return already proves no throw, so the redundant .not.toThrow() wrappers are removed. Greptile: sonnet-4.6 and opus-4.5 resolved values were never asserted. All four canonical alias mappings are now verified, giving complete regression coverage without extra verbosity.
|
Addressed both review findings (commit 75f325c): Greptile — redundant Greptile — incomplete value coverage: Added assertions for all four canonical mappings ( |
|
Both findings addressed: Greptile (id 2933291044) — Fixed in commit P2 (id 2933300653) — Acknowledged. The unit test cannot recreate the circular-import TDZ failure mode because by the time the test body runs, |
Addresses two review findings on PR openclaw#45397: Greptile: the test called parseModelRef twice for opus-4.6 and sonnet-4.5 (once inside .not.toThrow(), result discarded, and once for the value assertion). A non-null return already proves no throw, so the redundant .not.toThrow() wrappers are removed. Greptile: sonnet-4.6 and opus-4.5 resolved values were never asserted. All four canonical alias mappings are now verified, giving complete regression coverage without extra verbosity.
75f325c to
31fdee6
Compare
…nfig load (openclaw#45368) When the bundler flattens the module graph it may evaluate model-selection.ts before the module-level const ANTHROPIC_MODEL_ALIASES binding is live, due to circular-dependency ordering between: model-selection.ts -> config/config.js (re-exports config/io.js) -> config/defaults.ts -> agents/model-selection.js <-- cycle This causes a ReferenceError at startup: Cannot access 'ANTHROPIC_MODEL_ALIASES' before initialization at normalizeAnthropicModelId (...) at loadConfigSnapshot (...) Fix: replace the module-level const with a getter function getAnthropicModelAliases(). The object is now allocated at call time, which is always after all module-level bindings have been initialised, regardless of the bundler's module evaluation order. Fixes openclaw#45368, also fixes openclaw#45363 (same root cause, object-style model config).
Addresses two review findings on PR openclaw#45397: Greptile: the test called parseModelRef twice for opus-4.6 and sonnet-4.5 (once inside .not.toThrow(), result discarded, and once for the value assertion). A non-null return already proves no throw, so the redundant .not.toThrow() wrappers are removed. Greptile: sonnet-4.6 and opus-4.5 resolved values were never asserted. All four canonical alias mappings are now verified, giving complete regression coverage without extra verbosity.
31fdee6 to
6b0d227
Compare
Summary
Fixes #45368
Fixes #45363
Two separate bug reports describe the same crash:
This error occurs at startup (or on
openclaw status) and prevents the gateway from loading config correctly.Root Cause
ANTHROPIC_MODEL_ALIASESwas declared as a module-levelconstinmodel-selection.ts. When the bundler flattens the module graph it may evaluate this module before theconstbinding is live, due to a circular dependency:In ES modules,
constbindings are subject to the Temporal Dead Zone (TDZ): accessing them before the module that declares them has finished evaluating throws aReferenceError. BecausenormalizeAnthropicModelIdis called during config parsing (viaparseModelRef→normalizeModelRef), and config parsing is triggered early in the startup path, the TDZ is hit beforeANTHROPIC_MODEL_ALIASESis initialised.Fix
Replace the module-level
constwith a getter functiongetAnthropicModelAliases(). The object is now allocated at call time, which is always after all module-level bindings have been initialised, regardless of the bundler's module evaluation order.This is the minimal, lowest-risk fix: no imports change, no module boundaries move, no behaviour changes for callers.
Testing
Added a regression test that:
parseModelRefwith all four Anthropic shorthand aliasesReferenceErroris thrown