sipeed/picoclaw · error
mkdir media dir: %w
Error message
mkdir media dir: %w
What it means
os.MkdirAll(os.TempDir()/picoclaw_media, 0700) failed while spooling inbound WeCom media (media.go:327-329). The %w wraps an *fs.PathError whose errno names the real problem: EACCES/EROFS on read-only or foreign-owned temp dirs, or the path exists as a regular file.
Source
Thrown at pkg/channels/wecom/media.go:329
if err != nil {
return "", fmt.Errorf("decrypt media: %w", err)
}
}
filename, contentType := detectWeComMediaMetadata(
data,
msgID+fallbackExt,
resp.Header.Get("Content-Type"),
resourceURL,
resp.Header.Get("Content-Disposition"),
)
ext := filepath.Ext(filename)
if ext == "" {
ext = inferMediaExt(contentType, fallbackExt)
}
mediaDir := filepath.Join(os.TempDir(), "picoclaw_media")
if mkdirErr := os.MkdirAll(mediaDir, 0o700); mkdirErr != nil {
return "", fmt.Errorf("mkdir media dir: %w", mkdirErr)
}
tmpFile, err := os.CreateTemp(mediaDir, msgID+"-*"+ext)
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
tmpPath := tmpFile.Name()
if _, writeErr := tmpFile.Write(data); writeErr != nil {
_ = tmpFile.Close()
_ = os.Remove(tmpPath)
return "", fmt.Errorf("write temp file: %w", writeErr)
}
if closeErr := tmpFile.Close(); closeErr != nil {
_ = os.Remove(tmpPath)
return "", fmt.Errorf("close temp file: %w", closeErr)
}
ref, err := store.Store(tmpPath, media.MediaMeta{
Filename: filename,View on GitHub (pinned to 49183d7e8d)
Solutions
- Read the wrapped *fs.PathError: EACCES/EROFS means permissions/read-only, EEXIST-as-file means a stale file owns the name
- Set TMPDIR to a writable path for the process: Environment=TMPDIR=/var/lib/picoclaw/tmp (systemd) or ENV TMPDIR=/tmp/picoclaw (container with a writable mount)
- os.Stat the dir: if it exists as a file, remove it (rm $TMPDIR/picoclaw_media) once, then retry
- Verify with: TMPDIR=<dir> touch <dir>/probe from the same user the service runs as
Example fix
# before: container runs read-only, TMPDIR unset RUN chmod 555 /tmp # after: dedicated writable temp mount VOLUME ["/tmp"] ENV TMPDIR=/tmp
Defensive patterns
Strategy: validation
Validate before calling
// verify the spool dir is usable before media arrives
func mediaDirReady() error {
dir := filepath.Join(os.TempDir(), "picoclaw_media")
if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
return fmt.Errorf("%s exists and is not a directory", dir)
}
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
probe, err := os.CreateTemp(dir, "probe-*") // confirms write access
if err != nil {
return err
}
probe.Close()
return os.Remove(probe.Name())
} Prevention
- health-check the spool dir at startup, not on the first inbound media message
- pin TMPDIR explicitly in the service unit/container instead of inheriting the shell's
- monitor disk/inode headroom on the temp volume
When it happens
Trigger: TMPDIR points to a missing or read-only directory (hardened containers, scratch images); a previous run or another tool left a regular file named picoclaw_media in the temp dir; the process user lacks write permission on TMPDIR.
Common situations: Docker/Kubernetes containers with read-only rootfs and unset writable TMPDIR; running under systemd with PrivateTmp=true while TMPDIR points at a host path; multi-user hosts where /tmp is 1777 but a stale non-directory blocks the name.
Related errors
- credential: cannot read SSH key %q: %w
- create output dir: %w
- failed to save config: %w
- failed to save config: %w
- failed to stat local command %q: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/cacc61bd426c8b1f.
Report an issue: GitHub.