multica-ai/multica · error

read attachment %s: %w

Error message

read attachment %s: %w

What it means

An --attachment path passed the workdir guard and URL filter, but os.ReadFile then failed. Because the guard only checks path containment (not readability), this typically means the file is unreadable at read time: missing (removed between guard and read), permission denied, or a directory-like path. The %w wraps the OS error, and the message includes the offending path.

Source

Thrown at server/cmd/multica/cmd_issue.go:1031

// only accepts local paths). Each remaining path is run through the MUL-4252
// workdir guard and read into memory; the first invalid or unreadable path
// returns an error with nothing uploaded. Both `issue create` and
// `comment add` share this so an invalid attachment can never leave an earlier
// one uploaded as an orphaned issue attachment while the issue/comment is never
// created (which would duplicate on retry).
func collectLocalAttachments(cmd *cobra.Command, attachments []string) ([]pendingAttachment, error) {
	pending := make([]pendingAttachment, 0, len(attachments))
	for _, filePath := range attachments {
		if isHTTPURL(filePath) {
			fmt.Fprintf(os.Stderr, "Skipping --attachment %q: URLs are not supported here, only local file paths.\n", filePath)
			continue
		}
		if err := ensureAttachmentWithinWorkdir(cmd, filePath); err != nil {
			return nil, err
		}
		data, readErr := os.ReadFile(filePath)
		if readErr != nil {
			return nil, fmt.Errorf("read attachment %s: %w", filePath, readErr)
		}
		pending = append(pending, pendingAttachment{path: filePath, data: data})
	}
	return pending, nil
}

func appendUniqueStrings(dst []string, values ...string) []string {
	seen := make(map[string]struct{}, len(dst)+len(values))
	out := make([]string, 0, len(dst)+len(values))
	for _, v := range append(dst, values...) {
		v = strings.TrimSpace(v)
		if v == "" {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check the wrapped OS error: ENOENT → file vanished, regenerate it; EACCES → fix ownership/mode (chmod +r) or run as the owning user; EISDIR → point at the actual file.
  2. Verify the file exists and is readable immediately before the command: test -r <path>.
  3. Serialize artifact generation and attachment so cleanup jobs can't race the read.
  4. Regenerate the artifact inside the workdir if it was reaped from a shared location.

Example fix

# before (unreadable artifact)
multica issue create --title T --attachment ./chart.png   # mode 0000
# after
chmod 644 ./chart.png
multica issue create --title T --attachment ./chart.png
Defensive patterns

Strategy: validation

Validate before calling

# readable, regular, non-empty file before the CLI call
 [ -f "$ATTACH" ] && [ -r "$ATTACH" ] && [ -s "$ATTACH" ] \
  || { echo "attachment missing/unreadable/empty: $ATTACH" >&2; exit 1; }

Prevention

When it happens

Trigger: Running `multica issue create --attachment <path>` where the file is deleted by a concurrent process in the window between the guard check and ReadFile; mode 0000 or owned by another user; the path is a directory or otherwise not a readable regular file.

Common situations: Artifact cleanup jobs racing the attach step; files created root-only then attached by an unprivileged user; paths pointing at directories from templating mistakes.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/b946991455ad18d0. Report an issue: GitHub.