knadh/listmonk · error
failed to marshal Altcha challenge: %w
Error message
failed to marshal Altcha challenge: %w
What it means
GenerateChallenge wraps an error from json.Marshal when serializing the freshly created altcha.Challenge struct to JSON. This is nearly impossible with a well-formed challenge struct and indicates a structural problem — typically an unsupported field type (e.g. channel, func, or cyclic data) in the challenge object or an out-of-memory condition.
Source
Thrown at internal/captcha/captcha.go:134
// For Altcha, this returns a JSON challenge.
func (c *Captcha) GenerateChallenge() (string, error) {
switch c.provider {
case ProviderAltcha:
exp := time.Now().Add(5 * time.Minute)
challenge, err := altcha.CreateChallenge(altcha.ChallengeOptions{
Algorithm: altcha.SHA256,
MaxNumber: int64(c.altcha.Complexity),
SaltLength: 12,
HMACKey: c.altcha.HMACKey,
Expires: &exp,
})
if err != nil {
return "", fmt.Errorf("failed to create Altcha challenge: %w", err)
}
challengeJSON, err := json.Marshal(challenge)
if err != nil {
return "", fmt.Errorf("failed to marshal Altcha challenge: %w", err)
}
return string(challengeJSON), nil
case ProviderHCaptcha:
// hCaptcha generates challenges client-side.
return "", nil
default:
return "", fmt.Errorf("no captcha provider enabled")
}
}
// Verify verifies a CAPTCHA response.
func (c *Captcha) Verify(token string) (error, bool) {
switch c.provider {
case ProviderAltcha:
return c.verifyAltcha(token)
case ProviderHCaptcha:
return c.verifyHCaptcha(token)View on GitHub (pinned to 670c01717d)
Solutions
- Check the version of the altcha library and whether its Challenge type changed recently (go.mod diff).
- Marshal the challenge manually into a plain struct of JSON-safe fields if customization added non-serializable members.
- Unwrap with errors.As to confirm it is a *json.UnsupportedTypeError and log the offending field type.
Example fix
// before: marshal library struct directly
challengeJSON, err := json.Marshal(challenge)
// after: map to explicit JSON-safe struct
out := struct {
Algorithm string `json:"algorithm"`
Challenge string `json:"challenge"`
Salt string `json:"salt"`
Signature string `json:"signature"`
}{challenge.Algorithm, challenge.Challenge, challenge.Salt, challenge.Signature}
challengeJSON, err := json.Marshal(out) Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: ensure the challenge marshals in a test
var _ = func() error {
ch, err := altcha.Challenge(altcha.ChallengeOptions{MaxNumber: 1000, SaltLength: 8, HMACKey: "test"})
if err != nil {
return err
}
_, err = json.Marshal(ch)
return err
} Try / catch
challenge, err := captcha.GenerateChallenge(ctx)
if err != nil {
if strings.Contains(err.Error(), "failed to marshal Altcha challenge") {
log.Error("challenge not serializable — check altcha library version", "err", err)
http.Error(w, "captcha unavailable", http.StatusInternalServerError)
return
}
} Prevention
- Review altcha library diffs on upgrade for changes to the Challenge struct
- Never embed funcs/channels or cyclic references in challenge wrappers
- Add a CI test that generates and marshals a challenge end-to-end
When it happens
Trigger: Calling GenerateChallenge with ProviderAltcha after altcha.Challenge succeeds but json.Marshal(challenge) fails, e.g. after a library upgrade changes the challenge struct to contain a non-serializable field.
Common situations: Upgrading the altcha dependency introduces a non-marshalable field; custom Challenge wrappers add fields like func or chan; extreme memory pressure.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- failed to create Altcha challenge: %w
- failed to verify captcha solution: %w
- captcha verification failed
- error parsing lang file: %s: %v
- error unmarshalling postmark notification: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/47784a4a3fb90ac1.
Report an issue: GitHub.