# Opus Review

**Model:** claude-opus-4
**Generated:** 2026-03-09T23:30:00Z

---

## Critical Issues

### 1. Godot 4.5 Logger API -- Hard Dependency on Unreleased/Newly-Released Engine Version

The entire error detection approach in Section 3 hinges on the Godot 4.5 `Logger` class (`OS.add_logger()`). The plan acknowledges this in Section 10 ("Logger Availability") but treats it as a minor edge case: "the Logger registration will fail silently (class doesn't exist)."

This is a much bigger problem than suggested. If the target users are on Godot 4.3 or 4.4 (the current stable releases as of the plan date), the core feature of this sprint -- detailed error messages from the Logger -- simply does not work. The fallback to `load() + can_instantiate()` alone gives you pass/fail per script but no line numbers, no error messages, and no error types. The plan should make this explicit: **the plan needs a robust non-Logger path that is more than a fallback**, or it needs to declare Godot 4.5 as a hard minimum requirement and enforce that in the addon.

**Recommendation:** Either (a) declare Godot 4.5+ as a hard requirement and add a version check that disables the addon or shows a clear error on older versions, or (b) build the `check_errors` handler primarily around `load() + can_instantiate()` + `reload()` error codes and treat the Logger as an enhancement that adds line-number detail when available. The plan currently conflates these two paths.

### 2. `_handle_run` Pre-check Turns Synchronous Flow into Async (Section 5)

The current `_handle_run` at line 222 of `godotiq_server.gd` is a synchronous function -- it calls `EditorInterface.play_*()` immediately and sets up `_pending_run` for polling in `_process`. The plan's pre-run check (Section 5.1) says to load and check scripts before calling `play_*()`. However:

- Section 4.2 (the `check_errors` handler) says to yield with `await get_tree().process_frame` every N scripts. If the same pattern is used in the pre-run check, `_handle_run` itself becomes an async coroutine (`await`-based).
- Section 5.2 says "the pre-run check uses the simpler `load() + can_instantiate()` approach" and is "faster" -- but loading and instantiating every scene script plus autoloads could still block the main thread for hundreds of milliseconds on a medium project.
- The current `_handle_run` returns `void` and the response is sent asynchronously via `_pending_run` polling. If you add a pre-check that might return an error response before `play_*()` is called, you need to be careful not to send two responses for the same request ID.

**Recommendation:** Explicitly state whether `_handle_run` will become `async` or remain synchronous. If it stays synchronous, document maximum acceptable blocking time and cap the number of scripts checked. If it becomes async, document the interaction with the existing `_pending_run` polling mechanism.

### 3. Pre-run Check: `PackedScene` Instantiation is Dangerous (Section 5.3)

Section 5.3 suggests: "Load the PackedScene resource for the target scene, instantiate it temporarily, walk the node tree to collect script paths, free the temporary instance."

This is risky. Instantiating a scene runs `_init()` and `@onready` variable initialization. If those scripts have side effects (creating files, connecting signals, modifying autoloads), you get unintended behavior. More importantly, if the scripts being checked have errors, instantiating will fail or produce warnings.

**Recommendation:** Instead of instantiating the PackedScene, parse the `.tscn` file to extract script references. The codebase already has a TSCN parser at `src/godotiq/parsers/tscn_parser.py`. Alternatively, use `PackedScene._bundled` as the plan briefly mentions (the parenthetical "or use `_bundled` property for scripts") -- but this should be the primary approach, not a parenthetical alternative.

### 4. Response Format Inconsistency (Section 5.1)

Section 5.1 shows two contradictory response formats for pre-run errors:

```gdscript
_send_error(peer_id, id, "SCRIPT_ERRORS", "Cannot start game: ...")
# But actually use send_response with error details:
send_response(peer_id, id, {
    "status": "error",
    "code": "SCRIPT_ERRORS",
    ...
})
```

These are fundamentally different wire formats. `_send_error` produces `{"status": "error", "error": {"code": ..., "message": ...}}`. `send_response` produces `{"status": "ok", "result": {...}}`. The Python `BridgeConnection.request()` at `connection.py` raises `BridgeError` for error status and returns the result dict for ok status. If you use `send_response` with a dict containing `"status": "error"`, the bridge considers it a success and the Python side gets a normal dict back, requiring special handling.

**Recommendation:** Pick one. If you use `_send_error`, the Python bridge wrapper will catch it as a `BridgeError` and handle it through the standard error path (which already exists in `__init__.py`). If you use `send_response` with error data embedded in the result, you need custom handling in the Python wrapper. The `_send_error` approach is more consistent with existing patterns.

## Moderate Issues

### 5. Mutex Usage Pattern is Unclear (Section 3.2)

The plan says "The Logger writes to this buffer (with mutex). The `check_errors` handler runs on the main thread, so reads during the scan are safe as long as we lock/unlock properly." But then in Section 4.2, the handler clears the buffer at step 1 and reads it at step 4. Between those steps, the handler calls `reload()` on scripts, which triggers Logger callbacks. If the Logger callback happens on another thread, there is a window between clearing and reading where:

1. Handler clears buffer (on main thread, holding mutex briefly)
2. Handler calls `reload()` on script (on main thread)
3. Logger captures error (possibly on different thread, acquires mutex, writes to buffer)
4. Handler reads buffer (on main thread, acquires mutex)

This looks correct but the plan does not specify whether `_clear_script_errors` and `_get_script_errors` each acquire/release the mutex independently, or if there is a broader critical section. The step-by-step suggests independent lock/unlock, which should work, but deserves explicit documentation.

**Recommendation:** Add a concrete code sketch of the mutex acquire/release pattern for each operation. Clarify that the Logger callback mutex acquisition and the handler mutex acquisition never nest (which would deadlock).

### 6. `ResourceLoader.CACHE_MODE_IGNORE` May Not Exist in All Godot 4.x Versions (Section 10)

The plan uses `ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)`. The `CACHE_MODE_IGNORE` enum was added in Godot 4.1. If there is any chance of supporting Godot 4.0, this will fail. Given the Logger requires 4.5+, this is probably fine, but the `load() + can_instantiate()` fallback path (used when Logger is unavailable) should also use cache-busting.

### 7. Autoload Discovery API is Speculative (Sections 4.4 and 10)

Section 4.4 says `_get_scene_script_paths` will "add autoload scripts from `ProjectSettings.get_autoload_list()` (or similar API)." There is no `get_autoload_list()` method in Godot. The actual approach requires iterating ProjectSettings properties matching the `autoload/*` pattern:

```gdscript
for prop in ProjectSettings.get_property_list():
    if prop.name.begins_with("autoload/"):
        var value: String = ProjectSettings.get_setting(prop.name)
        var path: String = value.lstrip("*")
```

**Recommendation:** Replace the speculative `get_autoload_list()` reference with the actual pattern.

### 8. The `detail` Parameter on `godotiq_check_errors` is Unused (Section 6.2)

The MCP tool signature includes `detail: str = "normal"` but the plan never describes what different detail levels would show. Either remove it or define its behavior.

### 9. Test Coverage Gaps (Section 8)

The test plan only covers unit tests for the Python wrapper. It does not test:
- The MCP tool registration in `__init__.py`
- Scope validation (invalid paths)
- Empty project (no `.gd` files)
- The pre-run check interaction (GDScript side, requires integration test)

**Recommendation:** Add registration tests and edge case tests.

### 10. No `apply_output_limit` Call on the New Tool

Every other bridge tool in `__init__.py` either calls `apply_output_limit(result, detail)` or handles detail-level filtering. The plan's MCP tool never calls it.

## Minor Issues

### 11. Filesystem Scanner Excludes Only `addons/` (Section 4.3)

Also exclude `.godot/`, `build/`, `export/`.

### 12. Deduplication Logic is Underspecified (Section 4.2, Step 5)

Clarify whether all unique (file, line, message) tuples are kept, or only first error per file+line.

### 13. The `scope` Parameter Default is Inconsistent

Section 4.2 default is `"project"`. Section 6.1/6.2 default is `"scene"`. Should be explicitly called out.

### 14. Prompt Changes Reference a Tool That May Not Exist Yet

Section 7.2 references `godotiq_check_errors(scope="scene")` but if prompt changes ship before the tool handler, the agent will call a non-existent tool.

### 15. `_handle_run` Does Not Currently Save the Scene Before Playing

If the pre-run check loads scripts from disk, it might check stale on-disk versions. Consider triggering a save or reloading from editor's in-memory state.

### 16. The 50-Entry `_script_errors` Buffer May Be Too Small

A single broken base class could cascade into dozens of errors. Consider making this configurable or documenting truncation.

## Architectural Observations

### 17. Two Error Buffers, One `_editor_state`

The plan introduces `_script_errors` alongside existing `_recent_errors`. Having the Logger write to both buffers could pollute runtime error reporting.

**Recommendation:** Keep `_recent_errors` for runtime errors only. Add `script_errors` to `_editor_state` instead.

### 18. The Plan Mixes Concerns in `godotiq_server.gd`

`godotiq_server.gd` is already 1573 lines. Consider extracting Logger and error-checking logic into a separate `godotiq_error_checker.gd`.

## Summary

The plan addresses real and important gaps. The core approach (Logger + `load()/can_instantiate()/reload()`) is sound for Godot 4.5+. The main risks are:

1. **Hard dependency on Godot 4.5 Logger** with an under-specified fallback path
2. **Response format ambiguity** between `_send_error` and `send_response` for pre-run errors
3. **Scene instantiation side effects** in the pre-run check -- use TSCN parsing instead
4. **Dual error buffer confusion** that could pollute runtime error reporting
5. **Missing test coverage** for the MCP registration layer, edge cases, and output limiting

These should all be resolved before implementation begins.
