{"record":{"id":"74966815810372ab","repo":"grafana/k6","slug":"stack-too-big","errorCode":null,"errorMessage":"stack too big","messagePattern":"stack too big","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"js/modules/require_impl.go","lineNumber":217,"sourceCode":"func getPreviousRequiringFile(vu VU) (string, error) {\n\trt := vu.Runtime()\n\tvar buf [1000]sobek.StackFrame\n\tframes := rt.CaptureCallStack(1000, buf[:0])\n\n\tfor i, frame := range frames[1:] { // first one should be the current require\n\t\t// TODO have this precalculated automatically\n\t\tif frame.FuncName() == \"go.k6.io/k6/v2/internal/js.(*requireImpl).require-fm\" {\n\t\t\t// we need to get the one *before* but as we skip the first one the index matches ;)\n\t\t\tresult := frames[i].SrcName()\n\t\t\tif result == \"file:///-\" {\n\t\t\t\treturn vu.InitEnv().CWD.JoinPath(\"./-\").String(), nil\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\t}\n\t// hopefully nobody is calling `require` with 1000 big stack :crossedfingers:\n\tif len(frames) == 1000 {\n\t\treturn \"\", errors.New(\"stack too big\")\n\t}\n\n\t// fallback\n\tresult := frames[len(frames)-1].SrcName()\n\tif result == \"file:///-\" {\n\t\treturn vu.InitEnv().CWD.JoinPath(\"./-\").String(), nil\n\t}\n\treturn result, nil\n}\n\n// sets the provided promise in such way as to ignore falures\n// this is mostly needed as failures are handled separately and we do not want those to lead to stopping the event loop\nfunc promisesThenIgnore(rt *sobek.Runtime, promise *sobek.Promise) {\n\tcall, _ := sobek.AssertFunction(rt.ToValue(promise).ToObject(rt).Get(\"then\"))\n\thandler := rt.ToValue(func(_ sobek.Value) {})\n\t_, _ = call(rt.ToValue(promise), handler, handler)\n}\n","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/js/modules/require_impl.go#L199-L235","documentation":"getPreviousRequiringFile() walks up to 1000 captured stack frames looking for the internal require wrapper frame ('(*requireImpl).require-fm') to identify the file that triggered a require (mostly for legacy open() semantics). If the stack reaches 1000 frames without finding it, k6 gives up with 'stack too big' rather than guessing the caller. This is an internal guard, not a normal user error.","triggerScenarios":"Calling require()/open() from inside ~1000-deep recursion or a very long call chain, so the wrapper frame falls outside the 1000-frame window captured by rt.CaptureCallStack(1000, ...).","commonSituations":"Deeply recursive helper functions that eventually require() or open() a file; machine-generated scripts with enormous call chains; bundler output that inlines huge module trees. Extremely rare in practice.","solutions":["Hoist the require()/open() call to module top level instead of calling it deep in recursion","Reduce recursion depth (convert to iteration or memoize) if the call site must stay nested","If it reproduces with a reasonably shallow stack, report it as a k6 bug with the script"],"exampleFix":"// before (deep inside recursion)\nfunction walk(n) { if (n === 0) { data = open('./f.json'); return; } walk(n - 1); }\n\n// after (top level)\nconst data = open('./f.json');\nfunction walk(n) { /* uses the preloaded data */ }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// in JS: hoist open()/require() to top level so this internal guard never triggers;\n// in Go callers of getPreviousRequiringFile, treat the error as non-fatal and fall back to a known file.","preventionTips":["Call open() and require() at module top level, not deep in recursion","Convert deep recursion to iteration when it must load files","Report reproducible cases with shallow stacks as k6 bugs"],"tags":["javascript","modules","require","stack","recursion","internals"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}