Fix #398: alias shared classes via non-Mcp* preload consts on parse-hazard load surface#405
Conversation
…azard load surface The v2.3.2 → v2.4.0 self-update emitted ~30 transient `Parse Error` lines from five files that referenced new audit-v2 members (`McpErrorCodes.MISSING_REQUIRED_PARAM` and friends, `McpDock._make_header`, `UpdateReloadRunner.INSTALL_BACKUP_SUFFIX`) via bare class_name during the disable→extract→enable window, when the cached pre-update class objects were still the v2.3.2 forms. The new plugin loaded fine afterward, but during the window the affected scripts were unloaded — anything `_enter_tree` touched in them silently no-oped. Port the existing `connection.gd` / `dispatcher.gd` mitigation pattern into the five files: alias each shared class via a `const X := preload(...)` local const whose name does NOT shadow the global `class_name`, so GDScript resolves the lookup through the local-const preload-by-path rather than the registry. `update_mixed_state.gd` couldn't take that pattern (its const initializer ran at script-load and would still hit the cache); inline the literal `.update_backup` and add a Python lint that asserts the literal matches the producer's `INSTALL_BACKUP_SUFFIX` so anti-drift survives. Extend `TARGETED_LOAD_SURFACE_FILES` to cover the five files. The broader deny-by-default inversion across the whole load surface is tracked in #399.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
- log_viewer.gd: collapse the 9-line parse-hazard rationale into a short redirect to _node_validator.gd's canonical block, matching the established pattern in animation_presets.gd / animation_values.gd. Move the unique field-vs-parameter detail next to the field it applies to (`var _log_buffer`). - _node_validator.gd: revert an unintentional `McpErrorCodes` → `ErrorCodes` rename inside a docstring on require_scene_or_error. Comments don't trigger the parse hazard, and line 30's docstring still uses `McpErrorCodes`, so use the caller-facing global class_name consistently in both docstrings. https://claude.ai/code/session_012BsvjNEcChtXj1ZkTbnv9c
There was a problem hiding this comment.
Pull request overview
This PR hardens the Godot plugin’s self-update reload window (“disable → extract → enable”) against transient GDScript parse errors caused by parse-time resolution through the global class_name registry. It does so by routing shared-class access through script-local preload(...) aliases (with non-Mcp* names) on the known “reload load-surface”, and by replacing one problematic const alias with an inlined literal plus a Python anti-drift regression test.
Changes:
- Expand the self-update safety lint’s targeted file set and add a regression test that enforces
BACKUP_SUFFIXstays identical toINSTALL_BACKUP_SUFFIXwithout reintroducing the parse hazard. - Replace bare
Mcp*member access in key handler/dock scripts with non-Mcp*const X := preload(...)aliases to avoid registry lookups during the reload window. - Inline
BACKUP_SUFFIXinupdate_mixed_state.gd(instead of aliasingUpdateReloadRunner.INSTALL_BACKUP_SUFFIX) and guard against drift via unit test.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_plugin_self_update_safety.py | Extends targeted load-surface lint coverage and adds a suffix anti-drift test. |
| plugin/addons/godot_ai/utils/update_mixed_state.gd | Inlines backup suffix literal to avoid reload-window parse hazards and documents rationale. |
| plugin/addons/godot_ai/handlers/animation_values.gd | Adds preload aliases (ErrorCodes, PropertyErrors) and migrates member access off bare Mcp*. |
| plugin/addons/godot_ai/handlers/animation_presets.gd | Adds preload aliases (ErrorCodes, ScenePath) and migrates member access off bare Mcp*. |
| plugin/addons/godot_ai/handlers/_node_validator.gd | Renames preload aliases to non-Mcp* names and adds the central parse-hazard rationale comment. |
| plugin/addons/godot_ai/dock_panels/log_viewer.gd | Uses preload alias for dock helper calls and removes typed field annotation that can trigger parse-time registry lookups. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
dsarno
left a comment
There was a problem hiding this comment.
/review — automated daemon review
Overview
Fixes #398: transient Parse Error lines during self-update from v2.3.2 → v2.4.0 because audit-v2 added new members to shared Mcp* classes referenced via bare class_name from 5 files outside the existing parse-hazard allow-list. Routes those references through script-local non-Mcp* const X := preload(...) aliases so resolution bypasses the cached pre-update class_name registry, and inlines one literal that can't take that pattern (module-level const initializer).
Strengths
- Surgical scope. Only touches the 5 files surfaced by #398. The broader deny-by-default inversion is correctly deferred to #399 — one issue, one PR per CLAUDE.md.
- Self-documenting comments.
_node_validator.gdcarries the canonical parse-hazard rationale; the other 4 files each have a short preamble pointing back to it. Future readers don't have to git-blame to understand why these aliases exist. - Anti-drift coverage for the inlined literal. New
test_update_backup_suffix_stays_in_syncregex-extracts bothINSTALL_BACKUP_SUFFIX(producer) andBACKUP_SUFFIX(consumer) and asserts they match. Replaces the prior runtime-aliasing guard with a build-time one. Docstring explains why the runtime pattern can't be used at script-load on this path. - Convention alignment. Non-
Mcp*local consts (Dock,ScenePath,ErrorCodes,PropertyErrors) intentionally don't shadow the globalclass_name, which is the entire point of the fix and matches the CLAUDE.mdMcp*-prefix policy in spirit. - Style consistency.
_node_validator.gdmigrates fromconst X = preload(...)→const X := preload(...), matching the codebase's:=inference convention. - Documented type-fence trade-off.
_log_buffergoing untyped is explicit and the inline comment notes the preserved type-fence on thesetup()parameter. - CI fully green — all 15 checks pass, including Godot tests on Linux/macOS/Windows and game-capture smokes.
Minor observations (non-blocking)
- Drift surface for
BACKUP_SUFFIX. The new test pairsupdate_mixed_state.gd↔update_reload_runner.gdonly. If a future PR adds a third consumer of the suffix as a literal, it could drift undetected. Acceptable today (single consumer); worth keeping in mind. - Live self-update smoke not run from the daemon. CLAUDE.md mandates
script/local-self-update-smokefor changes that touch the install path, andupdate_mixed_state.gdis on it. PR description correctly flags this as a recommended step before merge. - Residual exposure outside the allow-list. A 6th file added later that references shared class members via bare
class_namewould reproduce the noise on the next release. That's exactly what #399 is scoped to fix.
Tests
- Existing
test_targeted_load_scripts_do_not_access_members_via_class_namelint now exercises the 5 newly-listed files. - New
test_update_backup_suffix_stays_in_sync— build-time anti-drift guard. - 899 Python tests pass;
script/ci-check-gdscriptclean; Godot suites green on all 3 OSes.
Risk
Low. Pure refactor — runtime semantics unchanged. Member access through a local-const preload is the same dereference cost as through class_name. Renames are mechanical and CI exercises every handler path.
Verdict
LGTM. Recommend the maintainer run script/local-self-update-smoke before merge per CLAUDE.md (PR description already calls this out).
Generated by Claude Code
The 3-line "see _node_validator.gd for the parse-hazard rationale" comments above the new preload aliases in animation_presets.gd, animation_values.gd, and log_viewer.gd's `Dock` const broke parity with the existing alias sites (connection.gd:24, dispatcher.gd:21) that established this pattern with no per-file rationale. The naming convention (non-`Mcp*`) is the self-documenting indicator; readers chasing rationale can grep the canonical block in _node_validator.gd directly. Also trims update_mixed_state.gd's 10-line BACKUP_SUFFIX comment and the test docstring on test_update_backup_suffix_stays_in_sync to ~3 lines each (the assertion messages already carry the detail) and fixes a stale `McpErrorCodes.make(...)` mention in _node_validator.gd's docstring that the rename left behind. The `_log_buffer` untyped explanation in log_viewer.gd is preserved (compressed to 3 lines) because the typed-field hazard is a distinct WHY from the alias rename and isn't covered by _node_validator.gd's block. https://claude.ai/code/session_01Up5h4CYx6CESJYXpQ8nNLr
|
Summary
The
v2.3.2 → v2.4.0self-update emitted ~30 transientParse Errorlines from five files that referenced new audit-v2 members (McpErrorCodes.MISSING_REQUIRED_PARAMand friends,McpDock._make_header,UpdateReloadRunner.INSTALL_BACKUP_SUFFIX) via bare class_name during the disable→extract→enable window. The new plugin loaded fine afterward, but the affected scripts were unloaded during the window — anything_enter_treetouched in them silently no-oped.The fix ports the existing
connection.gd/dispatcher.gdmitigation pattern into the five files: alias each shared class via aconst X := preload(...)local const whose name does not shadow the globalclass_name, so GDScript routes the lookup through the local-const preload-by-path resolution rather than the registry (which during the parse window holds the cached pre-update class object).utils/update_mixed_state.gd:20couldn't take that pattern — the const initializer ran at script-load and would still hit the cache whenupdate_reload_runner.gd's new constants weren't yet visible. Inlined the literal.update_backupinstead and added a Python lint that asserts the literal stays in lockstep with the producer'sINSTALL_BACKUP_SUFFIX.Also extends
TARGETED_LOAD_SURFACE_FILESto cover the five files so the existing typed-field / constructor / member-access lints prevent the same shape from recurring on this surface. The broader deny-by-default inversion across the whole load surface is tracked in #399.Closes #398.
Files changed
plugin/addons/godot_ai/handlers/_node_validator.gd— rename local const aliasesMcpScenePath/McpErrorCodes→ScenePath/ErrorCodes; add a comment block explaining the parse-hazard rationale that other handlers cite.plugin/addons/godot_ai/handlers/animation_presets.gd— addErrorCodesandScenePathpreload aliases; rename ~70 bare-class_name references.plugin/addons/godot_ai/handlers/animation_values.gd— addErrorCodesandPropertyErrorspreload aliases; rename ~20 bare-class_name references.plugin/addons/godot_ai/dock_panels/log_viewer.gd— addDockpreload alias formcp_dock.gd; untype_log_bufferfield (type fence stays on thesetup(log_buffer: McpLogBuffer)parameter).plugin/addons/godot_ai/utils/update_mixed_state.gd— drop theUpdateReloadRunnerpreload alias; inlineBACKUP_SUFFIX := ".update_backup"literal with anti-drift Python regression test.tests/unit/test_plugin_self_update_safety.py— extendTARGETED_LOAD_SURFACE_FILESto cover the five files; addtest_update_backup_suffix_stays_in_sync.Test plan
ruff check src/ tests/— cleanruff format --check src/ tests/— cleanpytest -v— 899 passed (includes the newtest_update_backup_suffix_stays_in_syncand the extended targeted-load lints over the new files)script/ci-check-gdscript—All GDScript files OKGodot tests(Linux/macOS/Windows) covers the GDScript handler tests, andscript/local-self-update-smokeis the right next step for end-to-end verification of the install/extract path before a release. Recommended for maintainer before merging sinceupdate_mixed_state.gdsits on the install path.Out of scope (per #399)
handlers/*.gdfiles that still reference shared classes by bare class_name. They aren't currently failing because v2.4.0 didn't add new members behind their references; Self-update parse-hazard lint: drop class_name from heavy internals (McpErrorCodes first) #399 tracks the structurally correct fix.Generated by Claude Code