{"record":{"id":"9bcfe268d0429243","repo":"charmbracelet/crush","slug":"failed-to-create-server-working-directory-v","errorCode":null,"errorMessage":"failed to create server working directory: %v","messagePattern":"failed to create server working directory: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/root.go","lineNumber":629,"sourceCode":"\n// quickHealthProbe issues a single readiness request with the caller's\n// context and returns nil iff the server is responsive right now.\nfunc quickHealthProbe(ctx context.Context, hostURL *url.URL) error {\n\thttpClient, reqURL, err := readinessHTTPClient(hostURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn probeHealth(ctx, httpClient, reqURL, hostURL)\n}\n\n// perHostServerDir returns (and creates) the cache directory used for\n// per-host server state (logs, start.lock, etc.). The path is derived\n// from the parsed host URL rather than the global flag so the same key\n// is computed regardless of where the host came from.\nfunc perHostServerDir(hostURL *url.URL) (string, error) {\n\tchDir := filepath.Join(config.GlobalCacheDir(), \"server-\"+safeHostName(hostURL))\n\tif err := os.MkdirAll(chDir, 0o700); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create server working directory: %v\", err)\n\t}\n\treturn chDir, nil\n}\n\n// safeHostName returns a filesystem-safe identifier for hostURL,\n// suitable for use as a directory name. It mirrors the input shape of\n// the --host flag so client and server compute the same key.\nfunc safeHostName(hostURL *url.URL) string {\n\treturn safeNameRegexp.ReplaceAllString(\n\t\thostURL.Scheme+\"://\"+hostURL.Host+hostURL.Path, \"_\",\n\t)\n}\n\n// serverReadyTimeout returns the total budget for the readiness probe.\n// Overridable via CRUSH_SERVER_READY_TIMEOUT (parsed as a Go duration).\nfunc serverReadyTimeout() time.Duration {\n\tconst def = 10 * time.Second\n\tv := os.Getenv(\"CRUSH_SERVER_READY_TIMEOUT\")","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/root.go#L611-L647","documentation":"perHostServerDir builds the per-host server state directory under config.GlobalCacheDir() ('server-<safe-host-name>') and creates it with os.MkdirAll(0o700). Any MkdirAll failure is wrapped as 'failed to create server working directory'. Called by spawnAndWaitReady and startDetachedServer, so any server spawn aborts.","triggerScenarios":"os.MkdirAll fails because the cache dir's parents don't exist and can't be created, permission is denied on an existing parent, the target path exists as a regular file, or the filesystem is full/read-only.","commonSituations":"XDG_CACHE_HOME/HOME pointing at a nonexistent or unwritable location; a file named like the expected directory left by a bug; read-only container rootfs; disk quota exceeded; running with a different $USER than the cache owner.","solutions":["Check that config.GlobalCacheDir()'s path (respecting XDG_CACHE_HOME) exists and is writable by the current user.","If a regular file occupies the 'server-<host>' path, remove or rename it.","Free disk space / raise quota if the filesystem is full.","Fix HOME/XDG_CACHE_HOME environment variables before launching crush."],"exampleFix":"// before\n// no pre-check; os.MkdirAll error aborts\nchDir, err := perHostServerDir(hostURL)\n// after\nif info, statErr := os.Stat(config.GlobalCacheDir()); statErr == nil && !info.IsDir() {\n\treturn nil, fmt.Errorf(\"cache dir %s is a file, not a directory\", config.GlobalCacheDir())\n}\nchDir, err := perHostServerDir(hostURL)","handlingStrategy":"validation","validationCode":"cache := config.GlobalCacheDir()\nif info, err := os.Stat(cache); err != nil {\n\treturn fmt.Errorf(\"cache dir missing: %w\", err)\n} else if !info.IsDir() {\n\treturn fmt.Errorf(\"%s is not a directory\", cache)\n}\nprobe := filepath.Join(cache, \".write-test\")\nif err := os.WriteFile(probe, nil, 0o600); err != nil {\n\treturn fmt.Errorf(\"cache dir not writable: %w\", err)\n}\nos.Remove(probe)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify XDG_CACHE_HOME/HOME resolve to an existing, writable directory","Ensure the filesystem has free space and inodes","Never let a regular file occupy names the tool expects to be directories","Run as a consistent user so 0o700 dirs remain accessible"],"tags":["filesystem","directory","permissions","cache"],"backgroundTag":"directory-creation-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}