{"record":{"id":"85fa7b75e9ec4d77","repo":"grafana/k6","slug":"exported-teardown-must-be-a-function","errorCode":null,"errorMessage":"exported 'teardown' must be a function","messagePattern":"exported 'teardown' must be a function","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/bundle.go","lineNumber":227,"sourceCode":"\t\t\t\tdec.DisallowUnknownFields()\n\t\t\t\tif err = dec.Decode(&b.Options); err != nil {\n\t\t\t\t\tif uerr := json.Unmarshal(data, &b.Options); uerr != nil {\n\t\t\t\t\t\t// Beautify the error so we can try to show the user the key and potential value that is failing to parse\n\t\t\t\t\t\tuerr = beautifyOptionsJSONUnmarshalError(data, uerr)\n\t\t\t\t\t\terr = errext.WithAbortReasonIfNone(\n\t\t\t\t\t\t\terrext.WithExitCodeIfNone(uerr, exitcodes.InvalidConfig),\n\t\t\t\t\t\t\terrext.AbortedByScriptError,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tb.preInitState.Logger.WithError(err).Warn(\"There were unknown fields in the options exported in the script\")\n\t\t\t\t\terr = nil\n\t\t\t\t}\n\t\t\tcase consts.SetupFn:\n\t\t\t\terr = errors.New(\"exported 'setup' must be a function\")\n\t\t\t\treturn\n\t\t\tcase consts.TeardownFn:\n\t\t\t\terr = errors.New(\"exported 'teardown' must be a function\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n\t<-ch\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(b.callableExports) == 0 {\n\t\treturn errors.New(\"no exported functions in script\")\n\t}\n\n\treturn nil\n}\n\nfunc beautifyOptionsJSONUnmarshalError(data []byte, err error) error {\n\tunmarshalTypError := new(json.UnmarshalTypeError)","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/bundle.go#L209-L245","documentation":"Same export scan as `setup`: k6 requires an export named `teardown` to be a callable function so it can run it once after all iterations. A non-function teardown binding is rejected during bundle loading (bundle.go:227).","triggerScenarios":"`export const teardown = true`, `export let teardown = {}`, or importing and re-exporting a non-function value under the name `teardown` in the entry script.","commonSituations":"Feature-flag objects like `export const teardown = false` meant to disable teardown; refactors that changed teardown into data; typos such as `teardownFn`.","solutions":["Declare it as a function: `export function teardown() { ... }`","To conditionally skip teardown, remove the export or guard inside the function instead of exporting a boolean","Rename unrelated data exports away from the reserved name"],"exampleFix":"// before\nexport const teardown = false;\n\n// after\nexport function teardown(data) { if (!data) return; /* cleanup */ }","handlingStrategy":"type-guard","validationCode":"if (typeof teardown !== 'undefined' && typeof teardown !== 'function') { throw new Error('teardown export must be a function'); }","typeGuard":"const isValidTeardown = (v) => v === undefined || typeof v === 'function';","tryCatchPattern":null,"preventionTips":["Never use `export const teardown = false` as a toggle — just remove the export","Reserve the names setup/teardown exclusively for lifecycle functions","CI-lint for non-function exports named teardown"],"tags":["javascript","script-structure","lifecycle","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}