Hive Hive
Sign in

fix(cli): graph recursion overflow

GitHub issue · Closed

Metadata
Source
tuist/tuist #11350
Updated
Jun 24, 2026
Domains
Generated projects
Details

Hello Tuist team!

Problem

Large valid Tuist dependency graphs can crash tuist generate with a stack overflow when graph-processing code uses recursive traversal.

For most projects this is unlikely to be noticeable because their target graphs are small or shallow. However, we hit this in a sufficiently large generated target graph on Tuist 4.182.0: after a routine project change, the graph shape changed enough to increase traversal depth, and tuist generate started crashing. The graph was valid and acyclic; the crash was caused by recursive graph processing, not by circular dependencies or invalid manifests.

Despite that we originally hit this in our project on Tuist 4.182.0, the same class of issue is still reproducible on current main at the time of validation (4.201.0-canary.9).

What Changed

This PR replaces several recursive graph traversals with iterative implementations:

  • CircularDependencyLinter
  • GraphCircularDetector
  • topologicalSort, moved into TuistSupport
  • ModuleMapMapper
  • StaticProductsGraphLinter

The local topologicalSort replaces the implementation imported from TSC. The upstream source, swiftlang/swift-tools-support-core, is already deprecated, so fixing it there would not be useful for Tuist.

The changes keep existing behavior and diagnostics, but use explicit stacks instead of relying on the process call stack.

Why This Helps

The affected code now handles deep dependency paths without growing the Swift call stack per graph node. This makes graph processing stack-safe for large acyclic workspaces while preserving existing caching, ordering, and linting behavior where relevant.

Validation

I created a standalone fixture generator to validate the fix:

igooor-bb/tuist-large-dag-repro

The fixture generates a large valid DAG that is intentionally closer to a real large workspace than a single linked list: multiple projects, feature layers, shared infrastructure, bridge targets with high fan-in, and no dependency cycles.

Primary validation command:

TUIST_BIN=/path/to/tuist ./scripts/reproduce.sh stress --no-focus

Focused-target validation path:

TUIST_BIN=/path/to/tuist ./scripts/reproduce.sh stress --focus

For more details about the fixture structure, available presets, and reproduction commands, see the fixture repository README.

Expected result:

  • unpatched binary crashes with stack overflow in one of the recursive graph-processing paths;
  • patched binary: generation completes successfully on the same fixture.

Test Coverage

The new long-chain tests in ModuleMapMapper and StaticProductsGraphLinter mostly verify that traversal no longer crashes due to recursion depth. They do not try to fully prove result equivalence on a very long chain. Existing shorter tests already cover preservation of transitive metadata and warning behavior, so I would not treat this as a blocker. I can still add stronger long-chain result assertions in this PR, or follow up separately with an issue.

Notes

  • The original issue in our project appeared on Tuist 4.182.0 and first surfaced in CircularDependencyLinter. I could not perfectly reproduce our production graph shape in a standalone fixture, but the fixture became a stronger stress test. After fixing the first crash, it exposed the next recursive risks one by one in later graph-processing stages.

  • I could not reproduce a stack overflow specifically through GraphCircularDetector, but it used recursive graph traversal as well and had the same potential failure mode on sufficiently deep dependency paths.

Comments
P
pepicrft Jun 18, 2026

Thanks a lot for this @igooor-bb 🙏🏼. The changes look good, so I’m going to merge once we run all the CI checks successfully.

IB
igooor-bb Jun 18, 2026

@pepicrft I see several tests have failed. I’ll check how they relate to my changes

P
pepicrft Jun 18, 2026

Please, don’t force-push anymore. I’m taking over this one ;)

TA
tuist-atlas[bot] Jun 19, 2026

The fix for graph recursion overflow is now available in version 4.201.0-canary.13. Update to that version to get the iterative graph traversal implementations that prevent stack overflow on large dependency graphs.

TA
tuist-atlas[bot] Jun 19, 2026

The fix for graph recursion overflow is now available in 4.201.0-canary.12. Recursive graph traversals in CircularDependencyLinter, GraphCircularDetector, topologicalSort, ModuleMapMapper, and StaticProductsGraphLinter have been replaced with iterative implementations, preventing stack overflow crashes on large dependency graphs. Update to this version to get the fix.

TA
tuist-atlas[bot] Jun 20, 2026

The fix for graph recursion overflow is now available in 4.201.0-canary.18. Update to this version to get it.

TA
tuist-atlas[bot] Jun 20, 2026

The fix for graph recursion overflow is now available in 4.201.0-canary.17. Update to this version to handle large dependency graphs without stack overflow.

TA
tuist-atlas[bot] Jun 20, 2026

The fix for graph recursion overflow is now available in 4.201.0-canary.16. Large dependency graphs now use iterative traversal instead of recursive traversal to avoid stack overflow crashes. Update to this version.

TA
tuist-atlas[bot] Jun 20, 2026

The fix for graph recursion overflow is now available in 4.201.0-canary.15. This replaces several recursive graph traversals with iterative implementations, making graph processing stack-safe for large acyclic workspaces. Update to this version to use the fix.

TA
tuist-atlas[bot] Jun 20, 2026

The fix for graph recursion overflow is now available in version 4.201.0-canary.14. Update to this version to prevent stack overflow crashes when processing large dependency graphs.

TA
tuist-atlas[bot] Jun 21, 2026

A fix for graph recursion overflow is now available in 4.201.0-canary.19. This replaces recursive graph traversals with iterative implementations in several components, preventing stack overflows on large valid dependency graphs. Update to 4.201.0-canary.19 to get this fix.