{"record":{"id":"e8b5b76fabb7169a","repo":"tailscale/tailscale","slug":"unable-to-create-userdata-init-d","errorCode":null,"errorMessage":"unable to create /userdata/init.d","messagePattern":"unable to create /userdata/init\\.d","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/tailscale/cli/configure-jetkvm.go","lineNumber":52,"sourceCode":"\t\tLongHelp: strings.TrimSpace(`\nThis command configures the JetKVM host to run tailscaled at boot.\n`),\n\t\tFlagSet: (func() *flag.FlagSet {\n\t\t\tfs := newFlagSet(\"jetkvm\")\n\t\t\treturn fs\n\t\t})(),\n\t}\n}\n\nfunc runConfigureJetKVM(ctx context.Context, args []string) error {\n\tif len(args) > 0 {\n\t\treturn errors.New(\"unknown arguments\")\n\t}\n\tif runtime.GOOS != \"linux\" || distro.Get() != distro.JetKVM {\n\t\treturn errors.New(\"only implemented on JetKVM\")\n\t}\n\tif err := os.MkdirAll(\"/userdata/init.d\", 0755); err != nil {\n\t\treturn errors.New(\"unable to create /userdata/init.d\")\n\t}\n\terr := os.WriteFile(\"/userdata/init.d/S22tailscale\", bytes.TrimLeft([]byte(`\n#!/bin/sh\n# /userdata/init.d/S22tailscale\n# Start/stop tailscaled\n\ncase \"$1\" in\n  start)\n    /userdata/tailscale/tailscaled > /dev/null 2>&1 &\n    ;;\n  stop)\n    killall tailscaled\n    ;;\n  *)\n    echo \"Usage: $0 {start|stop}\"\n    exit 1\n    ;;\nesac","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/tailscale/cli/configure-jetkvm.go#L34-L70","documentation":"os.MkdirAll('/userdata/init.d', 0755) failed while `tailscale configure jetkvm` was preparing the directory for its S22tailscale init script. The returned error is created with errors.New and does NOT wrap the underlying os error, so the real cause (permissions, read-only filesystem, path exists as a file, disk full) is hidden from the message. This discard-the-cause pattern makes diagnosis harder than it should be.","triggerScenarios":"Running the command as a non-root user on a JetKVM device; /userdata being mounted read-only or not mounted at all; an existing regular file named /userdata/init.d; a full userdata partition.","commonSituations":"Forgetting sudo on the JetKVM shell; the userdata partition not being mounted after a firmware reflash; leftover file occupying the init.d path from a previous manual install.","solutions":["Re-run as root: `sudo tailscale configure jetkvm`","Check the path manually: `ls -la /userdata/`, `mount | grep userdata`, `findmnt -no OPTIONS /userdata` (look for ro), and `df -h /userdata`","If a regular file blocks the directory, remove or rename it, then retry","Reproduce with `mkdir -p /userdata/init.d` to surface the real errno the CLI swallowed"],"exampleFix":"# before\n$ tailscale configure jetkvm\nerror: unable to create /userdata/init.d\n\n# after (diagnose, then retry)\n$ sudo mkdir -p /userdata/init.d && sudo tailscale configure jetkvm","handlingStrategy":"validation","validationCode":"# Preconditions for writing the JetKVM init script\n[ \"$(id -u)\" = \"0\" ] || { echo 'run as root'; exit 1; }\nmkdir -p /userdata/init.d 2>/dev/null || { echo 'cannot create /userdata/init.d'; mount | grep userdata; df -h /userdata; exit 1; }\nfindmnt -no OPTIONS /userdata | grep -qw ro && { echo '/userdata is read-only'; exit 1; }","typeGuard":null,"tryCatchPattern":"err := os.MkdirAll(\"/userdata/init.d\", 0o755)\nif err != nil {\n    return fmt.Errorf(\"creating /userdata/init.d: %w\", err) // keep the cause, unlike the CLI\n}","preventionTips":["Invoke the command via sudo on the JetKVM device","Confirm /userdata is mounted read-write before configuring","Reproduce filesystem errors with mkdir -p to surface the errno the CLI hides"],"tags":["filesystem","jetkvm","permissions","error-wrapping"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}