{"record":{"id":"bab3b9d632834b38","repo":"tailscale/tailscale","slug":"api-response-too-large","errorCode":null,"errorMessage":"API response too large","messagePattern":"API response too large","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"client/tailscale/tailscale.go","lineNumber":180,"sourceCode":"\t\treq.Header.Set(\"User-Agent\", c.UserAgent)\n\t}\n\treturn c.httpClient().Do(req)\n}\n\n// sendRequest add the authentication key to the request and sends it. It\n// receives the response and reads up to 10MB of it.\nfunc (c *Client) sendRequest(req *http.Request) ([]byte, *http.Response, error) {\n\tresp, err := c.Do(req)\n\tif err != nil {\n\t\treturn nil, resp, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Read response. Limit the response to 10MB.\n\tbody := io.LimitReader(resp.Body, maxReadSize+1)\n\tb, err := io.ReadAll(body)\n\tif len(b) > maxReadSize {\n\t\terr = errors.New(\"API response too large\")\n\t}\n\treturn b, resp, err\n}\n\n// ErrResponse is the HTTP error returned by the Tailscale server.\ntype ErrResponse struct {\n\tStatus  int\n\tMessage string\n}\n\nfunc (e ErrResponse) Error() string {\n\treturn fmt.Sprintf(\"Status: %d, Message: %q\", e.Status, e.Message)\n}\n\n// HandleErrorResponse decodes the error message from the server and returns\n// an [ErrResponse] from it.\n//\n// Deprecated: use [tailscale.com/client/tailscale/v2] instead.","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/client/tailscale/tailscale.go#L162-L198","documentation":"logpolicy.LogsDir walks a fallback chain to find a directory for log state: $TS_LOGS_DIR, platform dirs (STATE_DIRECTORY under systemd, /var/lib/tailscale, Windows ProgramData/LocalAppData), os.UserCacheDir, then the working directory (unless '/'), and finally os.MkdirTemp(\"\", \"tailscaled-log-*\"). This panic means every earlier option was unavailable AND creating a temp dir failed — the machine effectively offers no writable location, so tailscaled aborts at startup.","triggerScenarios":"Starting tailscaled in an environment where $TS_LOGS_DIR is unset/invalid, STATE_DIRECTORY is empty, /var/lib/tailscale is unwritable, UserCacheDir fails (no HOME/XDG_CACHE_HOME), cwd is '/' (typical under service managers), and MkdirTemp fails: read-only or full /tmp (disk exhausted, inode exhaustion), TMPDIR pointing to a nonexistent/unwritable directory, or ulimit/process restrictions.","commonSituations":"Containers (distroless/read-only rootfs, no writable /tmp, missing HOME), embedded systems with full flash, hardened systems with noexec/nodev/ro /tmp mounts, misconfigured TMPDIR, disk-full incidents, running tailscaled by hand from '/' after stripping env.","solutions":["Set TS_LOGS_DIR to an existing writable directory before starting tailscaled: `TS_LOGS_DIR=/var/log/tailscale tailscaled ...` (create the dir first).","Fix the temp dir: ensure /tmp (or $TMPDIR) exists, is mounted rw, and the filesystem/inodes aren't full (`df -h /tmp; df -i /tmp`).","If run under systemd, use the packaged unit with StateDirectory= (sets STATE_DIRECTORY) or add `StateDirectory=tailscale` to your unit.","In containers, mount a writable volume at /var/lib/tailscale or /tmp, or export HOME/XDG_CACHE_HOME to a writable path so UserCacheDir succeeds."],"exampleFix":"# before: read-only container, no writable anything\n$ docker run --read-only tailscale/tailscaled\npanic: no safe place found to store log state\n\n# after: give it a writable state dir\n$ docker run --read-only \\\n    -e TS_LOGS_DIR=/state/logs \\\n    -v tsstate:/state \\\n    tailscale/tailscaled","handlingStrategy":"validation","validationCode":"// before starting tailscaled, prove some log dir is writable\nfunc logDirWritable() error {\n    for _, d := range []string{\n        os.Getenv(\"TS_LOGS_DIR\"),\n        os.Getenv(\"STATE_DIRECTORY\"),\n        \"/var/lib/tailscale\",\n        os.TempDir(),\n    } {\n        if d == \"\" {\n            continue\n        }\n        if err := os.MkdirAll(d, 0o700); err == nil {\n            f, err := os.CreateTemp(d, \"probe-*\")\n            if err == nil {\n                f.Close()\n                os.Remove(f.Name())\n                return nil\n            }\n        }\n    }\n    return fmt.Errorf(\"no writable log directory; set TS_LOGS_DIR\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set TS_LOGS_DIR (or STATE_DIRECTORY under systemd) to a dedicated writable volume in containers and embedded hosts.","Monitor disk and inode usage on / and /tmp — MkdirTemp fails silently to the panic path when either is exhausted.","Keep TMPDIR pointing at an existing, rw-mounted directory in service environments.","Bake `mkdir -p /var/lib/tailscale` into container images so the default chain succeeds."],"tags":["go","tailscale","logpolicy","filesystem","temp-dir","startup","container"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}