{"record":{"id":"b18470c1065f8e73","repo":"sipeed/picoclaw","slug":"create-temp-file-w","errorCode":null,"errorMessage":"create temp file: %w","messagePattern":"create temp file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":333,"sourceCode":"\n\tfilename, contentType := detectWeComMediaMetadata(\n\t\tdata,\n\t\tmsgID+fallbackExt,\n\t\tresp.Header.Get(\"Content-Type\"),\n\t\tresourceURL,\n\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)","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L315-L351","documentation":"os.CreateTemp(mediaDir, msgID+\"-*\"+ext) failed while writing the downloaded inbound media (media.go:331-333). msgID arrives from the WeCom payload and is used verbatim as the pattern prefix, so a msgID containing '/' (or otherwise hostile bytes) makes CreateTemp target a nonexistent subdirectory. The %w wraps *fs.PathError: EACCES, ENOSPC, EMFILE (fd limit), EEXIST races, ENOENT when the spool dir vanished between MkdirAll and CreateTemp.","triggerScenarios":"msgID with path separators or illegal bytes; inode or fd exhaustion on a busy host; the spool dir deleted by a concurrent cleanup between MkdirAll and CreateTemp; disk full at file creation time.","commonSituations":"Long-running hosts leaking fds (EMFILE after days of uptime); /tmp on a small tmpfs out of inodes; parallel test/process runs racing to clean picoclaw_media; malformed upstream msgIDs after gateway version changes.","solutions":["Sanitize the prefix: pattern := filepath.Base(msgID) + \"-*\" + ext so separators can never reach CreateTemp","If EMFILE: raise ulimit -n / systemd LimitNOFILE and find the fd leak","If ENOSPC/inode exhaustion: df -h and df -i the temp fs, clean or enlarge","If a race with cleanup: serialize spool-dir creation/cleanup per process, or tolerate ENOENT with one MkdirAll+retry"],"exampleFix":"// before: raw platform msgID used as CreateTemp prefix\ntmpFile, err := os.CreateTemp(mediaDir, msgID+\"-*\"+ext)\n\n// after: sanitize to a single path element first\nprefix := filepath.Base(msgID)\nif prefix == \".\" || prefix == \"/\" || prefix == \"\" {\n    prefix = \"media\"\n}\ntmpFile, err := os.CreateTemp(mediaDir, prefix+\"-*\"+ext)","handlingStrategy":"validation","validationCode":"// precheck the pieces CreateTemp needs\nfunc canCreateTemp(prefix string) error {\n    if filepath.Base(prefix) != prefix || strings.ContainsAny(prefix, `/\u0000`) {\n        return fmt.Errorf(\"unsafe temp prefix %q\", prefix)\n    }\n    var stat unix.Statfs_t\n    if err := unix.Statfs(filepath.Join(os.TempDir(), \"picoclaw_media\"), &stat); err != nil {\n        return err\n    }\n    if stat.Ffree < 100 { // inode headroom\n        return fmt.Errorf(\"temp fs nearly out of inodes\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["never feed external IDs into os.CreateTemp patterns unsanitized - filepath.Base them first","alert on inode and fd usage of the media host, not just bytes","keep spool-dir lifecycle owned by one component to avoid create/cleanup races"],"tags":["temp-file","filesystem","inodes","sanitization"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}