{"record":{"id":"a2ab439d88ab7de2","repo":"tailscale/tailscale","slug":"error-reading-configfile-w","errorCode":null,"errorMessage":"error reading configfile: %w","messagePattern":"error reading configfile: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/containerboot/tailscaled.go","lineNumber":226,"sourceCode":"\tif w, err := fsnotify.NewWatcher(); err != nil {\n\t\t// Creating a new fsnotify watcher would fail for example if inotify was not able to create a new file descriptor.\n\t\t// See https://github.com/tailscale/tailscale/issues/15081\n\t\tlog.Printf(\"tailscaled config watch: failed to create fsnotify watcher, timer-only mode: %v\", err)\n\t\tticker := time.NewTicker(5 * time.Second)\n\t\tdefer ticker.Stop()\n\t\ttickChan = ticker.C\n\t} else {\n\t\tdefer w.Close()\n\t\tif err := w.Add(tailscaledCfgDir); err != nil {\n\t\t\terrCh <- fmt.Errorf(\"failed to add fsnotify watch: %w\", err)\n\t\t\treturn\n\t\t}\n\t\teventChan = w.Events\n\t\terrChan = w.Errors\n\t}\n\tb, err := os.ReadFile(path)\n\tif err != nil {\n\t\terrCh <- fmt.Errorf(\"error reading configfile: %w\", err)\n\t\treturn\n\t}\n\tprevTailscaledCfg = b\n\t// kubelet mounts Secrets to Pods using a series of symlinks, one of\n\t// which is <mount-dir>/..data that Kubernetes recommends consumers to\n\t// use if they need to monitor changes\n\t// https://github.com/kubernetes/kubernetes/blob/v1.28.1/pkg/volume/util/atomic_writer.go#L39-L61\n\tconst kubeletMountedCfg = \"..data\"\n\ttoWatch := filepath.Join(tailscaledCfgDir, kubeletMountedCfg)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase err := <-errChan:\n\t\t\terrCh <- fmt.Errorf(\"watcher error: %w\", err)\n\t\t\treturn\n\t\tcase <-tickChan:\n\t\tcase event := <-eventChan:","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/containerboot/tailscaled.go#L208-L244","documentation":"The initial os.ReadFile(path) of the tailscaled config file failed before the watch loop starts (the code requires the file to be readable at startup; only later re-reads happen inside the loop). The error is sent to errCh, which terminates containerboot's config watching.","triggerScenarios":"Config file absent while its directory exists (Secret mounted with a different key name); dangling symlink such as a broken kubelet ..data link; permission denied on the file itself; path points to a directory.","commonSituations":"ConfigMap key renamed so the mounted filename changed; Secret not yet projected on a slow node; file permissions 0600 owned by another UID; trailing slash or wrong filename in TS_TAILSCALED_CONFIG.","solutions":["Verify the exact file exists and is readable: ls -l /path/to/config && cat /path/to/config","If it is a kube Secret, follow the symlink chain and confirm ..data resolves: ls -la /mnt/secret/","Align the mounted key name with the expected filename","Fix file ownership/permissions for the container user"],"exampleFix":"# before: configmap key 'config.yaml' but containerboot reads '/etc/ts/tailscaled.conf'\n# after: set TS_TAILSCALED_CONFIG=/etc/ts/config.yaml (or rename the key)","handlingStrategy":"validation","validationCode":"if _, err := os.ReadFile(cfgPath); err != nil {\n\treturn fmt.Errorf(\"config unreadable at startup, fix mount: %w\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Smoke-test mounts in CI (render the pod spec, check the projected files exist)","Name ConfigMap/Secret keys to match the expected filename exactly","Prefer atomic projections (kubelet ..data) and avoid hand-managed symlinks"],"tags":["containerboot","filesystem","kubernetes","config"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}