{"record":{"id":"3ae447e0dd584187","repo":"golang/go","slug":"go-program-has-already-exited","errorCode":null,"errorMessage":"Go program has already exited","messagePattern":"Go program has already exited","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/wasm/wasm_exec.js","lineNumber":557,"sourceCode":"\t\t\t});\n\n\t\t\t// The linker guarantees global data starts from at least wasmMinDataAddr.\n\t\t\t// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.\n\t\t\tconst wasmMinDataAddr = 4096 + 8192;\n\t\t\tif (offset >= wasmMinDataAddr) {\n\t\t\t\tthrow new Error(\"total length of command line and environment variables exceeds limit\");\n\t\t\t}\n\n\t\t\tthis._inst.exports.run(argc, argv);\n\t\t\tif (this.exited) {\n\t\t\t\tthis._resolveExitPromise();\n\t\t\t}\n\t\t\tawait this._exitPromise;\n\t\t}\n\n\t\t_resume() {\n\t\t\tif (this.exited) {\n\t\t\t\tthrow new Error(\"Go program has already exited\");\n\t\t\t}\n\t\t\tthis._inst.exports.resume();\n\t\t\tif (this.exited) {\n\t\t\t\tthis._resolveExitPromise();\n\t\t\t}\n\t\t}\n\n\t\t_makeFuncWrapper(id) {\n\t\t\tconst go = this;\n\t\t\treturn function () {\n\t\t\t\tconst event = { id: id, this: this, args: arguments };\n\t\t\t\tgo._pendingEvent = event;\n\t\t\t\tgo._resume();\n\t\t\t\treturn event.result;\n\t\t\t};\n\t\t}\n\t}\n})();","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/lib/wasm/wasm_exec.js#L539-L575","documentation":"Thrown by Go._resume() in wasm_exec.js when the Go program has already set its exited flag and a resume is attempted. The Go wasm runtime uses cooperative scheduling via exported resume(); once the program has called runtime.exit, further resumes would dereference freed state, so they are blocked. It indicates the host kept dispatching callback events after the guest finished.","triggerScenarios":"An async Go callback (wrapped via _makeFuncWrapper) is invoked after the Go program has exited; the host code calls go._resume() manually; an event loop fires a queued Go callback after go.run()'s promise resolved with an exit code.","commonSituations":"Long-lived JS callbacks (DOM events, setInterval, server requests) that route into Go-wasm functions after the program has shut down; test harnesses that resume a Go instance after it returned non-zero; forgetting to tear down JS-side listeners when the Go program exits.","solutions":["Guard every JS-to-Go callback with a check: if (go.exited) return; before invoking the wrapped function.","Tear down JS-side event listeners, timers, and open requests in the .then/.catch of go.run().","If you must keep the runtime alive, ensure the Go program does not call os.Exit until all work is done."],"exampleFix":"// before\nconst cb = go._makeFuncWrapper(id);\nbutton.addEventListener('click', cb); // fires after Go exits -> throws\n\n// after\nconst cb = go._makeFuncWrapper(id);\nbutton.addEventListener('click', (...args) => {\n  if (go.exited) return;\n  return cb(...args);\n});","handlingStrategy":"type-guard","validationCode":"function safeCall(go, fn, ...args) {\n  if (go.exited) return undefined;\n  return fn(...args);\n}","typeGuard":"const isAlive = (go) => go.exited === false;","tryCatchPattern":null,"preventionTips":["Tear down JS-side listeners, timers, and open requests in the .then/.finally of go.run().","Guard every JS-to-Go callback entry point with a check on go.exited.","Do not call go._resume() manually; let the runtime schedule resumes."],"tags":["wasm","go","lifecycle","callbacks","state"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}