Skip to content

fix: prevent stack overflow in earlyResolve() on circular dependencies#2

Open
claude-do wants to merge 2 commits intomainfrom
fix/early-resolve-overflow
Open

fix: prevent stack overflow in earlyResolve() on circular dependencies#2
claude-do wants to merge 2 commits intomainfrom
fix/early-resolve-overflow

Conversation

@claude-do
Copy link
Collaborator

Summary

Fixes a stack overflow (RangeError: Maximum call stack size exceeded) in ConfigItem.earlyResolve() when the @currentEnv env flag item or @import enabled dependencies have circular references.

  • earlyResolve() runs during finishInit()before finishLoad() where findGraphCycles() provides cycle detection — so circular dependencies cause unbounded recursion and a process crash
  • Added isResolved idempotency guard (matching the existing guard in resolve()) to prevent redundant re-resolution on diamond dependencies
  • Added Set<string>-based cycle detection parameter, following the same recursionStack DFS pattern already used in findGraphCycles() (graph-utils.ts)
  • Wrapped the DirectoryDataSource._finishInit() call site in try/catch so cycle errors surface as _loadingError instead of uncaught exceptions

Changes

File Change
packages/varlock/src/env-graph/lib/config-item.ts Add isResolved guard + cycle detection via optional resolving Set param; fix typos
packages/varlock/src/env-graph/lib/data-source.ts Wrap earlyResolve() at line 704 in try/catch
packages/varlock/src/env-graph/test/environments.test.ts Add 3 tests: self-referencing cycle, indirect cycle, diamond dependency (no cycle)

Test plan

  • All 255 existing tests pass (bunx vitest --run)
  • New test: self-referencing env flag (APP_ENV=$APP_ENV) produces loading error, not crash
  • New test: indirect cycle via env flag (APP_ENV=$OTHER, OTHER=$APP_ENV) produces loading error
  • New test: diamond dependency (no cycle) in env flag resolves correctly
  • Lint clean (bun run lint:fix)

🤖 Generated with Claude Code

earlyResolve() runs during finishInit() — before finishLoad() where
findGraphCycles() provides cycle detection. Without guards, circular
dependencies among env flag items or @import enabled dependencies
cause unbounded recursion and a process crash.

Add an isResolved idempotency guard (matching resolve()), and a
Set<string>-based cycle detection parameter following the same
recursionStack DFS pattern used in findGraphCycles(). Wrap the
DirectoryDataSource call site in try/catch so cycle errors surface
as loading errors instead of uncaught exceptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mcorrig4 mcorrig4 force-pushed the fix/early-resolve-overflow branch from 1cf2d26 to 56ba732 Compare March 15, 2026 06:16
Captured from a real project (goalserve-poc demo-pwa) where
APP_ENV=fallback($APP_ENV, $VARLOCK_ENV, "development") in .env.schema
creates a self-referencing cycle that earlyResolve() follows infinitely.

Log files:
- varlock-oom-trace-1.log: OOM crash with 512MB Node heap cap
- varlock-oom-trace-2.log: OOM crash with 256MB Node heap cap
- varlock-env-info.log: Environment details (Node v22.22.1, varlock 0.4.1)
- varlock-post-fix-trace.log: Clean error output after applying the fix

Environment: Node v22.22.1, varlock 0.4.1, Linux x86_64, 8GB RAM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude-do
Copy link
Collaborator Author

Real-world reproduction & verification

Reproduced this bug in a real project (goalserve-poc demo-pwa) and confirmed the fix resolves it.

Root cause

The project's .env.schema contains:

# @currentEnv=$APP_ENV
APP_ENV=fallback($APP_ENV, $VARLOCK_ENV, "development")

APP_ENV references itself via fallback($APP_ENV, ...), creating a self-referencing cycle that earlyResolve() follows infinitely — exhausting the heap before findGraphCycles() ever runs.

Before the fix

varlock load crashes with OOM regardless of memory cap:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Aborted
  • With 512MB cap: OOM after ~8s (trace 1)
  • With 256MB cap: OOM after ~4s (trace 2)

After the fix

Clean, immediate error:

🚨 Error encountered while loading directory - /home/claude/code/goalserve-poc/apps/demo-pwa

Circular dependency detected during early resolution: APP_ENV

(post-fix trace)

Environment

  • Node v22.22.1
  • varlock 0.4.1
  • Linux x86_64, 8GB RAM

Full environment details in varlock-env-info.log.

The fix was also verified by patching the compiled chunk-PCRIVT4T.js directly in node_modules — both the earlyResolve() cycle detection and the _finishInit() try/catch behaved correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants