{"record":{"id":"dbca9d5f7c3da450","repo":"juanfont/headscale","slug":"headscale-exited-w","errorCode":null,"errorMessage":"headscale exited: %w","messagePattern":"headscale exited: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/dev/main.go","lineNumber":234,"sourceCode":"\t\t*port, metricsPort, metricsPort,\n\t\tconfigPath, tmpDir,\n\t\tauthKey,\n\t\t*port, authKey,\n\t\thsBin, configPath,\n\t\thsBin, configPath,\n\t)\n\n\t// Wait for headscale to exit.\n\terr = serve.Wait()\n\tif err != nil {\n\t\t// Context cancellation is expected on Ctrl+C.\n\t\tif ctx.Err() != nil {\n\t\t\tfmt.Println(\"\\nShutting down...\")\n\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"headscale exited: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// waitForHealth polls the health endpoint until it returns 200 or the\n// timeout expires.\nfunc waitForHealth(ctx context.Context, url string, timeout time.Duration) error {\n\tdeadline := time.Now().Add(timeout)\n\n\tfor time.Now().Before(deadline) {\n\t\tif ctx.Err() != nil {\n\t\t\treturn ctx.Err()\n\t\t}\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"creating request: %w\", err)","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/dev/main.go#L216-L252","documentation":"\"headscale exited: %w\" at cmd/dev/main.go:234 wraps serve.Wait() after the dev workflow finishes its setup and prints its banner. It fires when the spawned `headscale serve` process exits with a non-zero status for a reason other than Ctrl+C (ctx cancellation is checked first and returns nil). The child's stdout/stderr were forwarded, so the crash reason is already on the console.","triggerScenarios":"The server crashing at runtime after a healthy start: fatal runtime error after a config reload, database corruption, panic in a handler, port conflict introduced mid-run, or SIGTERM from outside. Not returned for clean Ctrl+C shutdown.","commonSituations":"Server panics on a code path under development (this is a dev harness); OOM killer terminating the process; another service binding the same port after startup.","solutions":["Scroll to the child's own panic/error output — the %w cause is usually just 'exit status 1/2'","If a panic, fix the reported stack in hscontrol; re-run cmd/dev to reproduce","If OOM/signal, free resources or stop whatever sent the signal, then re-run"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := serve.Wait(); err != nil {\n\tif ctx.Err() != nil {\n\t\treturn nil // Ctrl+C: expected shutdown, not an error\n\t}\n\t// child's panic/stack was already forwarded to stdout — read it there\n\treturn fmt.Errorf(\"headscale exited: %w\", err)\n}","preventionTips":["Check the forwarded child logs before assuming the harness is at fault","Run the server under the dev tool when reproducing panics; it keeps logs on your console"],"tags":["dev-tooling","process","runtime-crash","supervision"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}