Volta extends resolution in getNodeVersionFromFile has no cycle detection, recursing until stack overflow
@v-gowridurgad is already working on this.
Since Sep 11, 2026.
- Dominant language
- TypeScript
- Stars
- 5k
- Forks
- 1.7k
- Avg merge
- 3d 12h
- Merged PRs (30d)
- 2
Description
Summary
getNodeVersionFromFile follows Volta extends recursively with no cycle detection, so a self-referential or mutually-referential volta.extends chain causes unbounded recursion and crashes the action with a stack overflow instead of a clear error.
Location
- File:
src/util.ts - Function:
getNodeVersionFromFile(versionFilePath: string) - Code path:
if (manifest.volta?.extends) {
const extendedFilePath = path.resolve(
path.dirname(versionFilePath),
manifest.volta.extends
);
core.info('Resolving node version from ' + extendedFilePath);
return getNodeVersionFromFile(extendedFilePath);
}
No visited-path set or depth limit is threaded through the recursion.
Problem
Volta workspaces support {"volta": {"extends": "./base/package.json"}}. If the target (transitively) points back to a file already on the resolution stack — e.g. package.json with "extends": "./package.json" (typo), or a.json → b.json → a.json — the function recurses forever until V8 throws RangeError: Maximum call stack size exceeded. The user gets an opaque stack-overflow failure rather than a diagnostic naming the cycle.
Trigger / Reproduction
Based on static analysis (no workflow run performed):
- Set
node-version-fileto apackage.jsoncontaining:
or a two-file cycle{"volta": {"extends": "./package.json"}}a.json↔b.jsonviavolta.extends. - Run the action.
getNodeVersionFromFileresolvesextends, re-enters itself with the same path, and never terminates normally.
Note: this is a static-analysis finding; I did not execute a workflow against a cyclic fixture.
Expected Behavior
Cyclic volta.extends should fail fast with a clear error naming the files in the cycle (e.g. Detected cyclic volta.extends: a.json -> b.json -> a.json), consistent with how missing files already throw The specified node version file at: ... does not exist.
Actual Behavior
Unbounded recursion until stack exhaustion, producing an unactionable RangeError with no mention of the offending extends chain.
Impact
- A one-character typo in
volta.extendsturns a configuration mistake into an opaque action crash, costing debugging time. - No data-loss risk, but the failure mode hides the actual cause (cycle) behind a generic engine error.
Suggested Direction
- Thread a
seen: Set<string>(resolved absolute paths) throughgetNodeVersionFromFile, checking before recursing and throwing a descriptive cycle error. Alternatively cap recursion depth with the same diagnostic. Either preserves current behavior for acyclic chains.
Evidence
- Source via API:
src/util.tsVolta-extends branch shows direct unconditional recursion with no guard; neighboring branches (missing file, non-JSON/TOML fallthrough) all have explicit handling, highlighting the gap. - Duplicate check: issue search for
volta extends cycle recursionreturnstotal_count: 0, and the open-issue list contains no extends-cycle report (nearest is.nvmrccomment parsing) — no apparent duplicate.
What happened
Unbounded recursion on cyclic Volta extends as detailed above.
Expected behavior
Fast, descriptive cycle error instead of stack overflow.
Steps to reproduce
Point node-version-file at a self- or mutually-referential Volta extends fixture and observe getNodeVersionFromFile re-enter indefinitely (static path; runtime stack overflow implied).
reproducible code
// package.json
{"volta": {"extends": "./package.json"}}
- uses: actions/setup-node@v4
with:
node-version-file: package.json
manifest.yaml
N/A
Versions
- actions/setup-node: current
main(verified via API,src/util.ts) - Node: N/A (static analysis finding)
Classification
- FACT: Volta
extendsrecursion has no visited-set or depth cap (verified in source via API). - INFERENCE: cyclic fixtures therefore recurse until stack exhaustion.
- HYPOTHESIS: a visited-path guard produces a clear error with no change to valid chains.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.