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

  1. Read the wrapped *fs.PathError: EACCES/EROFS means permissions/read-only, EEXIST-as-file means a stale file owns the name
  2. 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)
  3. os.Stat the dir: if it exists as a file, remove it (rm $TMPDIR/picoclaw_media) once, then retry
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/cacc61bd426c8b1f. Report an issue: GitHub.