Repository navigation
fix(scan): propagate OOM as a hard failure + plug leak-on-OOM (#540) - #555
Conversation
The flow/script scan swallowed error.OutOfMemory on three paths,
violating the module's "OOM is a hard failure" design goal:
1. src/codegen/scan/flow_decls.zig — the plugin + game-script passes
did `scanFlowDeclsInSource(...) catch continue`, masking an OOM as a
tolerated per-file parse failure and silently leaving the generated
flow registry incomplete (wrong codegen, no error). The scan's
inferred error set is in fact *exactly* error{OutOfMemory}
(std.zig.Ast.parse records parse errors in the AST rather than
raising them), so both call sites now use an exhaustive
`catch |err| switch (err) { error.OutOfMemory => return err }`.
2. src/codegen/scan/promote.zig — a freshly-duped `rel_path` leaked if
`out.append(...)` OOMed before ownership transferred to the list;
added `errdefer allocator.free(rel_path)` right after the dupe.
3. src/root/game_shim.zig — `arr_list.toOwnedSlice(allocator)` leaked
the buffer on OOM after `toArrayList()` reset the writer; added
`errdefer arr_list.deinit(allocator)` (a no-op on the reset writer).
Adds FailingAllocator regression tests for all three sites: OOM
propagation for the scan (never a silent partial), and freed==allocated
balance at every allocation index for the two leak sites.
Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughThis PR fixes out-of-memory handling in three scan/shim-generation modules: ChangesOOM propagation and leak fixes
Estimated code review effort: 2 (Simple) | ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request fixes several out-of-memory (OOM) handling issues across the codebase. In flow_decls.zig, OOM errors are now correctly propagated as hard failures instead of being silently swallowed. In promote.zig and game_shim.zig, local errdefer blocks have been added to prevent memory leaks when OOM occurs during list appending or slice conversion. Regression tests using FailingAllocator have been added for each of these cases to ensure proper OOM propagation and leak prevention. There are no review comments, and I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/codegen/scan/flow_decls.zig (2)
255-262: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winPropagate
extractConstructsStringallocation failures.allocator.dupe(...) catch nullstill turnserror.OutOfMemoryinto “no.constructsfield,” so the scanner can silently drop a real value on memory pressure. Make this helper return!?[]u8andtrythe duplicate so OOM bubbles up like the rest of the scan.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/codegen/scan/flow_decls.zig` around lines 255 - 262, `extractConstructsString` is swallowing allocation failures by converting `allocator.dupe(...)` errors into `null`, which makes a real `.constructs` value look absent under memory pressure. Update this helper to return `!?[]u8`, use `try` for the duplicate, and propagate `error.OutOfMemory` through the caller chain in `scan/flow_decls.zig` so the scan fails consistently instead of silently dropping the value.
510-512: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
cache.resolvePlugin(...) catch continueshould not swallow OOMsrc/cache/resolve.zigallocates and joins paths here, soerror.OutOfMemoryis reachable; let this fail hard instead of silently skipping the plugin from the flow registry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/codegen/scan/flow_decls.zig` around lines 510 - 512, The plugin resolution loop is swallowing allocator failures by using catch continue on cache.resolvePlugin, which can hide error.OutOfMemory. Update the flow registry logic in the for (cfg.plugins) loop around plugin_dir so only expected non-fatal resolution errors are skipped, and let OutOfMemory propagate instead of continuing silently; use the cache.resolvePlugin and plugin_dir handling in this block to locate the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/codegen/scan/flow_decls.zig`:
- Around line 255-262: `extractConstructsString` is swallowing allocation
failures by converting `allocator.dupe(...)` errors into `null`, which makes a
real `.constructs` value look absent under memory pressure. Update this helper
to return `!?[]u8`, use `try` for the duplicate, and propagate
`error.OutOfMemory` through the caller chain in `scan/flow_decls.zig` so the
scan fails consistently instead of silently dropping the value.
- Around line 510-512: The plugin resolution loop is swallowing allocator
failures by using catch continue on cache.resolvePlugin, which can hide
error.OutOfMemory. Update the flow registry logic in the for (cfg.plugins) loop
around plugin_dir so only expected non-fatal resolution errors are skipped, and
let OutOfMemory propagate instead of continuing silently; use the
cache.resolvePlugin and plugin_dir handling in this block to locate the fix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3f1d3bb0-02d4-4231-a557-441c261c13e3
📒 Files selected for processing (3)
src/codegen/scan/flow_decls.zigsrc/codegen/scan/promote.zigsrc/root/game_shim.zig
Closes #540.
Pre-existing latent robustness issues in the flow/script scan (surfaced during the #539 split, present verbatim on
mainbefore it): three paths swallowederror.OutOfMemory, violating the module's documented "OOM is a hard failure" goal. All three are behavior changes on the OOM path only — normal codegen output is byte-identical (golden suite unchanged).The three sites
src/codegen/scan/flow_decls.zig— the plugin + game-script passes didscanFlowDeclsInSource(...) catch continue, masking an allocation failure as a tolerated per-file parse failure. A swallowed OOM silently leaves the generated flow registry incomplete → wrong codegen with no error. Fix:catch |err| switch (err) { error.OutOfMemory => return err }at both call sites.error{OutOfMemory}—std.zig.Ast.parserecords parse errors in the AST rather than raising them, so there was never a genuine per-file parse error to tolerate; the oldcontinueonly ever swallowed OOM. The switch is therefore exhaustive (noelse); if the scan ever grows a tolerable error it will stop compiling and force a deliberateelse => continue.src/codegen/scan/promote.zig— a freshly-dupedrel_pathleaked ifout.append(...)OOMed before ownership transferred to the list (the function-scope errdefer walksout.items, which doesn't yet include the pending entry). Fix:errdefer allocator.free(rel_path);right after the dupe.src/root/game_shim.zig(from the refactor: split root.zig into focused sub-modules (behavior-preserving) #542 root split) —arr_list.toOwnedSlice(allocator)leaked the buffer on OOM:toArrayList()reset the writer so the function-scopeerrdefer aw.deinit()covers nothing. Fix:errdefer arr_list.deinit(allocator);before thetoOwnedSlice(safe — the reset writer's deinit is a no-op).Tests
FailingAllocator regression tests (the toolkit's failing-allocator idiom) for each site:
discoverPluginFlowDecls: OOM propagates, never a silent partial scan— drives a game script with a realFlowNodesblock through a FailingAllocator at every alloc index; asserts each forced failure surfaces aserror.OutOfMemoryand never returns a value with a truncated node set (exactly what the oldcatch continueproduced). Balances freed==allocated at every point.collectPromotedScripts: no allocator leak at any OOM pointgenerateGameShim: no allocator leak at any OOM pointBar
zig build+zig build testgreen.zig fmt --checkclean.https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
Summary by CodeRabbit
Bug Fixes
Tests