FiloSottile/age · error

column limit exceeded

Error message

column limit exceeded

What it means

Armor lines are restricted to format.ColumnsPerLine (64) characters, matching the encoder's output. A longer line means the data was not produced by this armor format (or was reformatted), so the reader rejects it instead of guessing how to split it.

Solutions

  1. Re-wrap body lines to at most 64 columns (e.g. fold -w 64) and retry decryption.
  2. Re-obtain the armored file from its original source or re-run the age CLI to produce correctly wrapped armor.
  3. Disable editor word-wrap and avoid MIME re-encoding when transferring .age files.

Example fix

// before: one 200-char base64 line in the armor body
// after
fold -w 64 longline.age > wrapped.age
Defensive patterns

Strategy: validation

Validate before calling

// Check armor line widths before decrypting
func checkLineLengths(data []byte) error {
    for _, l := range bytes.Split(data, []byte('\n')) {
        l = bytes.TrimRight(l, "\r")
        if !bytes.HasPrefix(l, []byte("-----")) && len(l) > 64 {
            return fmt.Errorf("line too long: %d", len(l))
        }
    }
    return nil
}

Type guard

func isWrappedArmorLine(line []byte) bool {
    return len(line) <= 64
}

Try / catch

_, err := io.ReadAll(armor.NewReader(f))
if err != nil && err.Error() == "column limit exceeded" {
    // re-wrap lines with fold -w 64 or re-export the armor
}

Prevention

When it happens

Trigger: Reading an armored file where a body line exceeds 64 characters — e.g. armor reflowed by a text editor with a different wrap width, MIME base64 from email (76-char lines) pasted into an age armor envelope, or hand-concatenated base64 lines.

Common situations: Extracting base64 from a MIME email attachment into an age armor template, editors with word-wrap rewriting the file, or scripts joining base64 lines into one long line.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/fd39c917730b0b06. Report an issue: GitHub.

Appendix: source

Thrown at armor/armor.go:161

			continue
		}
		if string(line) != Header {
			return 0, r.setErr(fmt.Errorf("invalid first line: %q", line))
		}
		r.started = true
	}
	line, err := getLine()
	if err != nil {
		return 0, r.setErr(err)
	}
	if string(line) == Footer {
		return 0, r.setErr(drainTrailing())
	}
	if len(line) == 0 {
		return 0, r.setErr(errors.New("empty line in armored data"))
	}
	if len(line) > format.ColumnsPerLine {
		return 0, r.setErr(errors.New("column limit exceeded"))
	}
	// Reject newline characters ignored by base64.Decode.
	if bytes.ContainsAny(line, "\n\r") {
		return 0, r.setErr(errors.New("unexpected newline character"))
	}
	r.unread = r.buf[:]
	n, err := base64.StdEncoding.Strict().Decode(r.unread, line)
	if err != nil {
		return 0, r.setErr(err)
	}
	r.unread = r.unread[:n]

	if n < format.BytesPerLine {
		line, err := getLine()
		if err != nil {
			return 0, r.setErr(err)
		}
		if string(line) != Footer {

View on GitHub (pinned to b74dce4cdb)