Billionmail/BillionMail · error

parse maildirsize line %d: invalid size '%s': %w

Error message

parse maildirsize line %d: invalid size '%s': %w

What it means

readMaildirsize parses Dovecot's maildirsize file to compute a mailbox's used space. Each data line starts with a byte count that may be suffixed with 'S' (messages) which is stripped before parsing. This error is thrown when strconv.ParseInt cannot convert the size token to an int64, meaning the maildirsize file is corrupted or in an unexpected format.

Source

Thrown at core/internal/service/mail_boxes/update_used_space.go:226

		}

		parts := strings.Fields(line)
		if len(parts) == 0 {
			continue
		}

		if lineNo == 1 {

			continue
		}

		sizeStr := parts[0]

		sizeStr = strings.TrimSuffix(sizeStr, "S")

		v, err := strconv.ParseInt(sizeStr, 10, 64)
		if err != nil {
			return 0, fmt.Errorf("parse maildirsize line %d: invalid size '%s': %w", lineNo, sizeStr, err)
		}
		total += v
	}

	if err := scanner.Err(); err != nil {
		return 0, fmt.Errorf("scan maildirsize failed: %w", err)
	}

	return total, nil
}

// domain.current_usage
func aggregateDomainUsage(ctx context.Context) {

	if atomic.LoadInt32(&domainUsageColumnEnsured) == 0 {
		if _, err := g.DB().Exec(ctx, "ALTER TABLE domain ADD COLUMN IF NOT EXISTS current_usage BIGINT NOT NULL DEFAULT 0"); err != nil {
			g.Log().Warning(ctx, "ensure domain.current_usage column failed", err)
		}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Inspect the maildirsize file at the reported line number and fix or remove the malformed size token
  2. Delete the maildirsize file and let Dovecot recalculate it (rebuild quota index)
  3. Ensure quota rules are consistent so tools writing maildirsize use the standard '<bytes> <count>' format
  4. Wrap ParseInt with a guard that skips empty/unparseable tokens instead of failing the whole read

Example fix

// before
v, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil {
    return 0, fmt.Errorf("parse maildirsize line %d: invalid size '%s': %w", lineNo, sizeStr, err)
}
// after
if sizeStr == "" {
    continue // skip blank size token
}
v, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil {
    return 0, fmt.Errorf("parse maildirsize line %d: invalid size '%s': %w", lineNo, sizeStr, err)
}
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(maildirsizePath)
if err != nil { return err }
for i, line := range strings.Split(string(data), "\n") {
    if i == 0 { continue } // header
    sizeStr := strings.TrimSuffix(strings.Fields(line)[0], "S")
    if _, err := strconv.ParseInt(sizeStr, 10, 64); err != nil {
        return fmt.Errorf("malformed maildirsize line %d", i+1)
    }
}

Type guard

func isValidMaildirSize(tok string) bool {
    _, err := strconv.ParseInt(strings.TrimSuffix(tok, "S"), 10, 64)
    return err == nil
}

Try / catch

total, err := readMaildirsize(path)
if errors.Is(err, strconv.ErrSyntax) || strings.Contains(err.Error(), "invalid size") {
    log.Warn("corrupt maildirsize, rebuilding")
    os.Remove(path) // let Dovecot recalc
    return recalcUsageFromMaildir(path)
}
if err != nil { return err }

Prevention

When it happens

Trigger: A maildirsize line whose first whitespace-separated field is not a decimal integer (after trimming a trailing 'S'), e.g. alphabetic garbage, truncated writes, or a header line miscounted as a data line.

Common situations: Disk-full or crash during maildirsize write leaving a partial line; manual edits of the maildirsize file; a mail delivery tool writing a nonstandard size field; NFS corruption of the maildir metadata.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/07bb1f445926a1d2. Report an issue: GitHub.