{"record":{"id":"eee28fc5569b67fe","repo":"sipeed/picoclaw","slug":"write-temp-file-w","errorCode":null,"errorMessage":"write temp file: %w","messagePattern":"write temp file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":339,"sourceCode":"\t\tresp.Header.Get(\"Content-Disposition\"),\n\t)\n\text := filepath.Ext(filename)\n\tif ext == \"\" {\n\t\text = inferMediaExt(contentType, fallbackExt)\n\t}\n\tmediaDir := filepath.Join(os.TempDir(), \"picoclaw_media\")\n\tif mkdirErr := os.MkdirAll(mediaDir, 0o700); mkdirErr != nil {\n\t\treturn \"\", fmt.Errorf(\"mkdir media dir: %w\", mkdirErr)\n\t}\n\ttmpFile, err := os.CreateTemp(mediaDir, msgID+\"-*\"+ext)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create temp file: %w\", err)\n\t}\n\ttmpPath := tmpFile.Name()\n\tif _, writeErr := tmpFile.Write(data); writeErr != nil {\n\t\t_ = tmpFile.Close()\n\t\t_ = os.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"write temp file: %w\", writeErr)\n\t}\n\tif closeErr := tmpFile.Close(); closeErr != nil {\n\t\t_ = os.Remove(tmpPath)\n\t\treturn \"\", fmt.Errorf(\"close temp file: %w\", closeErr)\n\t}\n\n\tref, err := store.Store(tmpPath, media.MediaMeta{\n\t\tFilename:      filename,\n\t\tContentType:   contentType,\n\t\tSource:        \"wecom\",\n\t\tCleanupPolicy: media.CleanupPolicyDeleteOnCleanup,\n\t}, scope)\n\tif err != nil {\n\t\t_ = os.Remove(tmpPath)\n\t\treturn \"\", err\n\t}\n\treturn ref, nil\n}","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L321-L357","documentation":"Writing the downloaded inbound media bytes to the created temp file failed (media.go:336-339). Writes of up to 20 MiB go to os.TempDir()/picoclaw_media, so ENOSPC (disk/full quota) dominates; EIO and EDQUOT also surface here. The code already closes and unlinks the partial file, so no residue is left.","triggerScenarios":"tmpfs /tmp smaller than the media (containers default tmpfs to a fraction of RAM); disk quota hit mid-write; NFS/overlayfs write errors; physical disk full.","commonSituations":"Kubernetes emptyDir/tmpfs sized 64 MiB receiving a 20 MiB video plus other pods' usage; CI runners with tiny scratch disks; shared hosts with per-user quotas.","solutions":["df -h <TMPDIR> - if tmpfs too small, give the media path a real volume or raise the container's memory limit (tmpfs counts against RAM)","Set TMPDIR to the largest writable volume: ENV TMPDIR=/var/lib/picoclaw/tmp with a mounted PVC/volume","If quota (EDQUOT): raise the user quota or run the service under a user with headroom","Guard at the source: reject media near the limit when free space is insufficient"],"exampleFix":"# before: media spools to 64MiB tmpfs\nresources:\n  limits: { memory: 128Mi }\n\n# after: spool to a dedicated writable volume\nenv: [{ name: TMPDIR, value: /var/lib/picoclaw/tmp }]\nvolumeMounts: [{ name: media, mountPath: /var/lib/picoclaw/tmp }]","handlingStrategy":"validation","validationCode":"import \"golang.org/x/sys/unix\"\n\nfunc tmpHasSpace(needBytes uint64) bool {\n\tvar st unix.Statfs_t\n\tif err := unix.Statfs(os.TempDir(), &st); err != nil {\n\t\treturn false\n\t}\n\treturn uint64(st.Bavail)*uint64(st.Bsize) > needBytes+64<<20\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["monitor free space on the temp volume; alert well above the 20 MiB working set","prefer a persistent volume over tmpfs for the media spool","size limits assuming concurrency: N parallel 20 MiB transfers need N x 20 MiB of spool headroom"],"tags":["disk-full","filesystem","io","deployment"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}