wavetermdev/waveterm · error

failed to create socket directory: %w

Error message

failed to create socket directory: %w

What it means

MakeJobDomainSocket creates the per-user socket directory /tmp/waveterm-<uid> with os.MkdirAll(0700) before binding the job's domain socket. This error wraps any MkdirAll failure. It almost always means a filesystem/permission problem on /tmp rather than a bug in the caller.

Source

Thrown at pkg/jobmanager/jobmanager.go:383

	if err != nil {
		return fmt.Errorf("failed to attach stream writer: %w", err)
	}

	err = jm.StreamManager.SetRwndSize(int(jm.pendingStreamMeta.RWnd))
	if err != nil {
		return fmt.Errorf("failed to set rwnd size: %w", err)
	}

	log.Printf("StartStream: streamid=%s rwnd=%d streaming started\n", jm.pendingStreamMeta.Id, jm.pendingStreamMeta.RWnd)
	jm.pendingStreamMeta = nil
	return nil
}

func MakeJobDomainSocket(clientId string, jobId string) error {
	socketDir := filepath.Join("/tmp", fmt.Sprintf("waveterm-%d", os.Getuid()))
	err := os.MkdirAll(socketDir, 0700)
	if err != nil {
		return fmt.Errorf("failed to create socket directory: %w", err)
	}

	socketPath := wavebase.GetRemoteJobSocketPath(jobId)

	os.Remove(socketPath)

	listener, err := net.Listen("unix", socketPath)
	if err != nil {
		return fmt.Errorf("failed to listen on domain socket: %w", err)
	}

	go func() {
		defer func() {
			panichandler.PanicHandler("MakeJobDomainSocket:accept", recover())
			listener.Close()
			os.Remove(socketPath)
		}()
		for {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check whether /tmp/waveterm-<uid> exists as a file and remove it: ls -la /tmp | grep waveterm
  2. Verify /tmp is writable for the running uid; free space if ENOSPC
  3. Set TMPDIR to a writable directory if the environment restricts /tmp (if the code honors it; otherwise fix mounts)
  4. Inspect the wrapped error for EACCES/EROFS/ENOSPC and fix the corresponding mount/permission

Example fix

// before
if err := os.MkdirAll(socketDir, 0700); err != nil {
    return fmt.Errorf("failed to create socket directory: %w", err)
}
// after
if err := os.MkdirAll(socketDir, 0700); err != nil {
    if fi, serr := os.Lstat(socketDir); serr == nil && !fi.IsDir() {
        if rmErr := os.Remove(socketDir); rmErr == nil {
            return MakeJobDomainSocket(clientId, jobId)
        }
    }
    return fmt.Errorf("failed to create socket directory %s: %w", socketDir, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

sd := fmt.Sprintf("/tmp/waveterm-%d", os.Getuid())
if fi, err := os.Stat("/tmp"); err != nil || !fi.IsDir() {
    return fmt.Errorf("/tmp unavailable")
}
if fi, err := os.Lstat(sd); err == nil && !fi.IsDir() {
    return fmt.Errorf("%s exists and is not a directory", sd)
}

Try / catch

if err := MakeJobDomainSocket(clientId, jobId); err != nil {
    if strings.HasPrefix(err.Error(), "failed to create socket directory") {
        log.Printf("socket dir error: %v", errors.Unwrap(err))
        // check /tmp writability, ENOSPC, or blocking file
    }
    return err
}

Prevention

When it happens

Trigger: os.MkdirAll("/tmp/waveterm-<uid>", 0700) fails because /tmp is not writable, the path exists as a regular file, the filesystem is full or read-only, or a path component denies traversal.

Common situations: Container with read-only /tmp; sticky-bit /tmp with restrictive mount options; leftover regular file named waveterm-<uid> in /tmp; disk-full (ENOSPC) on tmpfs; SELinux/AppArmor denial.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/78235d57abada85c. Report an issue: GitHub.