{"record":{"id":"d22c3ecb35864669","repo":"plandex-ai/plandex","slug":"error-getting-num-tokens-v","errorCode":null,"errorMessage":"error getting num tokens: %v","messagePattern":"error getting num tokens: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/context_helpers_update.go","lineNumber":176,"sourceCode":"\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// log.Println(\"Got context\", context.Id, \"numTokens\", context.NumTokens)\n\t\t\t}\n\n\t\t\tmu.Lock()\n\t\t\tdefer mu.Unlock()\n\n\t\t\tcontextsById[id] = context\n\t\t\tupdatedContexts = append(updatedContexts, context.ToApi())\n\n\t\t\tif context.ContextType != shared.ContextMapType {\n\t\t\t\tvar updateNumTokens int\n\t\t\t\tvar err error\n\n\t\t\t\tif context.ContextType == shared.ContextImageType {\n\t\t\t\t\tupdateNumTokens, err = shared.GetImageTokens(params.Body, context.ImageDetail)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\terrCh <- fmt.Errorf(\"error getting num tokens: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tupdateNumTokens = shared.GetNumTokensEstimate(params.Body)\n\t\t\t\t\t// log.Println(\"len(params.Body)\", len(params.Body))\n\t\t\t\t}\n\n\t\t\t\t// log.Println(\"Updating context\", id, \"updateNumTokens\", updateNumTokens)\n\n\t\t\t\ttokenDiff := updateNumTokens - context.NumTokens\n\t\t\t\ttokenDiffsById[id] = tokenDiff\n\t\t\t\taggregateTokensDiff += tokenDiff\n\t\t\t\ttotalTokens += tokenDiff\n\t\t\t\ttotalPlannerTokens += tokenDiff\n\t\t\t\tif !context.AutoLoaded {\n\t\t\t\t\ttotalBasicPlannerTokens += tokenDiff\n\t\t\t\t\taggregateBasicTokensDiff += tokenDiff\n\t\t\t\t}","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/context_helpers_update.go#L158-L194","documentation":"UpdateContexts computes the token count for each context being updated. For image contexts it calls shared.GetImageTokens, which base64-decodes the image body and parses its header to compute width/height-based token estimates. This error wraps any failure from that call — most commonly invalid base64 data or an undecodable/corrupt image header.","triggerScenarios":"An UpdateContexts request supplies an image-type context whose params.Body is not valid standard base64 (e.g. data-URI prefix like 'data:image/png;base64,' left in, whitespace/newlines, URL-safe base64) or whose decoded bytes are not a recognizable image format (image.DecodeConfig fails on truncated/corrupt/unsupported formats).","commonSituations":"Client sends a data URL instead of raw base64; image was truncated by an upload size limit; user attached a non-image file renamed to .png; an image format without a registered decoder (e.g. webp/avif without the decoder imported) is passed through.","solutions":["Strip the 'data:image/...;base64,' prefix and other non-base64 characters from params.Body before sending","Verify the file is a real, complete image and in a format with a registered Go decoder (jpeg, png, gif); re-upload if truncated","Check server logs for 'failed to decode base64 image data' vs 'failed to decode image config' to pinpoint which stage failed","If using a newer format, ensure the corresponding image decoder package is imported for side effects in shared/images.go"],"exampleFix":"// before\nbody := \"data:image/png;base64,iVBORw0KGgo...\"\n// after\nbody := strings.TrimPrefix(raw, \"data:image/png;base64,\")\nif _, err := base64.StdEncoding.DecodeString(body); err != nil { return fmt.Errorf(\"invalid base64 image body\") }","handlingStrategy":"validation","validationCode":"func validImageBody(b string) bool {\n    b = stripDataURIPrefix(b)\n    data, err := base64.StdEncoding.DecodeString(b)\n    if err != nil || len(data) == 0 { return false }\n    _, _, err = image.DecodeConfig(bytes.NewReader(data))\n    return err == nil\n}\n// call before UpdateContexts for each image context","typeGuard":"func isDecodableImage(body string) (ok bool) {\n    defer func() { if recover() != nil { ok = false } }()\n    return validImageBody(body)\n}","tryCatchPattern":"if _, err := shared.GetImageTokens(params.Body, detail); err != nil {\n    log.Printf(\"skipping invalid image context %s: %v\", id, err)\n    return nil // drop the context instead of failing the batch\n}","preventionTips":["Strip data-URI prefixes and whitespace before base64-encoding image bodies","Only send jpeg/png/gif images (formats with registered Go decoders)","Validate with image.DecodeConfig client-side before uploading","Check file completeness (truncated uploads fail header parsing)"],"tags":["image","base64","context","go"],"backgroundTag":"invalid-image-data","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}