{"record":{"id":"47784a4a3fb90ac1","repo":"knadh/listmonk","slug":"failed-to-marshal-altcha-challenge-w","errorCode":null,"errorMessage":"failed to marshal Altcha challenge: %w","messagePattern":"failed to marshal Altcha challenge: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/captcha/captcha.go","lineNumber":134,"sourceCode":"// For Altcha, this returns a JSON challenge.\nfunc (c *Captcha) GenerateChallenge() (string, error) {\n\tswitch c.provider {\n\tcase ProviderAltcha:\n\t\texp := time.Now().Add(5 * time.Minute)\n\t\tchallenge, err := altcha.CreateChallenge(altcha.ChallengeOptions{\n\t\t\tAlgorithm:  altcha.SHA256,\n\t\t\tMaxNumber:  int64(c.altcha.Complexity),\n\t\t\tSaltLength: 12,\n\t\t\tHMACKey:    c.altcha.HMACKey,\n\t\t\tExpires:    &exp,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to create Altcha challenge: %w\", err)\n\t\t}\n\n\t\tchallengeJSON, err := json.Marshal(challenge)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to marshal Altcha challenge: %w\", err)\n\t\t}\n\n\t\treturn string(challengeJSON), nil\n\tcase ProviderHCaptcha:\n\t\t// hCaptcha generates challenges client-side.\n\t\treturn \"\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"no captcha provider enabled\")\n\t}\n}\n\n// Verify verifies a CAPTCHA response.\nfunc (c *Captcha) Verify(token string) (error, bool) {\n\tswitch c.provider {\n\tcase ProviderAltcha:\n\t\treturn c.verifyAltcha(token)\n\tcase ProviderHCaptcha:\n\t\treturn c.verifyHCaptcha(token)","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/captcha/captcha.go#L116-L152","documentation":"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.","triggerScenarios":"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.","commonSituations":"Upgrading the altcha dependency introduces a non-marshalable field; custom Challenge wrappers add fields like func or chan; extreme memory pressure.","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."],"exampleFix":"// before: marshal library struct directly\nchallengeJSON, err := json.Marshal(challenge)\n// after: map to explicit JSON-safe struct\nout := struct {\n  Algorithm string `json:\"algorithm\"`\n  Challenge string `json:\"challenge\"`\n  Salt      string `json:\"salt\"`\n  Signature string `json:\"signature\"`\n}{challenge.Algorithm, challenge.Challenge, challenge.Salt, challenge.Signature}\nchallengeJSON, err := json.Marshal(out)","handlingStrategy":"try-catch","validationCode":"// pre-flight: ensure the challenge marshals in a test\nvar _ = func() error {\n  ch, err := altcha.Challenge(altcha.ChallengeOptions{MaxNumber: 1000, SaltLength: 8, HMACKey: \"test\"})\n  if err != nil {\n    return err\n  }\n  _, err = json.Marshal(ch)\n  return err\n}","typeGuard":null,"tryCatchPattern":"challenge, err := captcha.GenerateChallenge(ctx)\nif err != nil {\n  if strings.Contains(err.Error(), \"failed to marshal Altcha challenge\") {\n    log.Error(\"challenge not serializable — check altcha library version\", \"err\", err)\n    http.Error(w, \"captcha unavailable\", http.StatusInternalServerError)\n    return\n  }\n}","preventionTips":["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"],"tags":["go","captcha","altcha","json","marshal"],"backgroundTag":"json-marshal-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}