{"record":{"id":"f9e6748691ffadb7","repo":"juanfont/headscale","slug":"starting-headscale-w","errorCode":null,"errorMessage":"starting headscale: %w","messagePattern":"starting headscale: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/dev/main.go","lineNumber":150,"sourceCode":"\tbuild := exec.CommandContext(ctx, \"go\", \"build\", \"-o\", hsBin, \"./cmd/headscale\")\n\tbuild.Stdout = os.Stdout\n\tbuild.Stderr = os.Stderr\n\n\terr = build.Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"building headscale: %w\", err)\n\t}\n\n\t// Start headscale serve.\n\tfmt.Println(\"Starting headscale server...\")\n\n\tserve := exec.CommandContext(ctx, hsBin, \"serve\", \"-c\", configPath)\n\tserve.Stdout = os.Stdout\n\tserve.Stderr = os.Stderr\n\n\terr = serve.Start()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"starting headscale: %w\", err)\n\t}\n\n\t// Wait for server to be ready.\n\thealthURL := fmt.Sprintf(\"http://127.0.0.1:%d/health\", *port)\n\n\terr = waitForHealth(ctx, healthURL, 30*time.Second)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"waiting for headscale: %w\", err)\n\t}\n\n\t// Create user.\n\tfmt.Println(\"Creating user and pre-auth key...\")\n\n\tuserJSON, err := runHS(ctx, hsBin, configPath, \"users\", \"create\", \"dev\", \"-o\", \"json\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating user: %w\", err)\n\t}\n","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/dev/main.go#L132-L168","documentation":"\"starting headscale: %w\" at cmd/dev/main.go:150 wraps serve.Start() of the freshly built `<tmpDir>/headscale serve -c <tmpDir>/config.yaml` in cmd/dev. Start() only fails if the process cannot be spawned at all (exec format error, permission denied, binary missing); runtime failures of the server show up later via the health check or serve.Wait. The child inherits stdout/stderr, so any exec-level message is on the console.","triggerScenarios":"The built binary was deleted or truncated before Start (antivirus/tmp reaper); exec.PermissionDenied because the tmpDir mount is noexec; cross-compilation environment producing a binary for the wrong architecture (exec format error).","commonSituations":"/tmp mounted noexec on hardened distros; building under a mismatched GOARCH; security tooling quarantining freshly compiled binaries.","solutions":["Set TMPDIR to an exec-allowed, local path (mount without noexec) and re-run","Confirm `go env GOOS GOARCH` matches the host before running cmd/dev","Disable/allowlist the binary in security software if it quarantines new executables"],"exampleFix":"# before (tmpdir on a noexec mount)\ngo run ./cmd/dev\n\n# after\nmkdir -p \"$HOME/tmp\" && TMPDIR=\"$HOME/tmp\" go run ./cmd/dev","handlingStrategy":"try-catch","validationCode":"// ensure the binary is executable on this host before Start\nif fi, err := os.Stat(hsBin); err != nil || fi.Mode()&0o111 == 0 {\n\treturn errors.New(\"dev binary missing or not executable; check TMPDIR mount flags (noexec?)\")\n}","typeGuard":null,"tryCatchPattern":"if err := serve.Start(); err != nil {\n\t// only spawn-time failures land here: noexec TMPDIR, wrong GOARCH, deleted binary\n\treturn fmt.Errorf(\"starting headscale: %w\", err)\n}","preventionTips":["Ensure TMPDIR is not mounted noexec","Match go env GOOS/GOARCH to the host when using the dev tool"],"tags":["dev-tooling","process","exec","environment"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}