chenhg5/cc-connect · error

write: %w

Error message

write: %w

What it means

saveAttachment writes the attachment with os.WriteFile(path, data, 0o644) and wraps failures as "write: %w". After the directory exists and the name is sanitized with filepath.Base, this error means the actual file write failed, so the attachment never reaches disk and SendFile/SendImage return without notifying the chat.

Source

Thrown at platform/wps-agentspace/wpsagentspace.go:282

// the absolute path. The filename is sanitized to a basename to prevent path
// traversal.
func (p *Platform) saveAttachment(name string, data []byte) (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("resolve home dir: %w", err)
	}
	dir := filepath.Join(home, ".cc-connect", "attachments")
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return "", fmt.Errorf("mkdir: %w", err)
	}

	name = filepath.Base(name)
	if name == "" || name == "." || name == "/" {
		name = fmt.Sprintf("file_%d", time.Now().UnixMilli())
	}
	path := filepath.Join(dir, name)
	if err := os.WriteFile(path, data, 0o644); err != nil {
		return "", fmt.Errorf("write: %w", err)
	}
	return path, nil
}

// Stop gracefully shuts down the platform.
func (p *Platform) Stop() error {
	p.stopOnce.Do(func() {
		p.stopped.Store(true)
		if p.cancel != nil {
			p.cancel()
		}
		p.mu.Lock()
		if p.conn != nil {
			_ = p.conn.Close()
		}
		p.mu.Unlock()
	})
	return nil

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Unwrap with errors.Is to classify: os.ErrPermission, os.ErrExist (path is a directory), or ENOSPC.
  2. Check free space (df -h ~/.cc-connect) and enlarge the volume or prune old attachments.
  3. Remove/replace a directory occupying the sanitized basename, or adjust disk quotas.
  4. Ensure the daemon user has write permission on ~/.cc-connect/attachments (chown/chmod).

Example fix

// before: no space check before writing large attachments
path, err := p.saveAttachment(name, img.Data)
// after (caller-side validation)
if len(img.Data) > 100<<20 {
    return fmt.Errorf("attachment %s too large (%d bytes)", name, len(img.Data))
}
path, err := p.saveAttachment(name, img.Data)
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight writability check
probe := filepath.Join(dir, ".write-test")
if err := os.WriteFile(probe, nil, 0o644); err != nil {
    return fmt.Errorf("attachments dir %s not writable: %w", dir, err)
}
os.Remove(probe)
if len(data) > 50<<20 {
    return fmt.Errorf("attachment %s exceeds 50MB limit", name)
}

Try / catch

if _, err := p.SendImage(rc, img); err != nil && strings.HasPrefix(err.Error(), "write:") {
    var pe *fs.PathError
    if errors.As(err, &pe) {
        slog.Error("attachment write failed", "path", pe.Path, "cause", pe.Err)
    }
}

Prevention

When it happens

Trigger: SendFile/SendImage when: the target path exists as a directory (sanitized basename collides with a directory name); disk full (ENOSPC); quota exceeded; permission denied on the directory; the file was created between MkdirAll and WriteFile with restrictive permissions.

Common situations: Small root filesystems filling up with large sent files; attachment directories on network mounts that go read-only; two daemons with different UIDs sharing the same $HOME; images with names identical to pre-existing directories.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/35e6fd759856a3ad. Report an issue: GitHub.