{"record":{"id":"e8023b6f3d8b9d9c","repo":"Tencent/WeKnora","slug":"state-done-but-no-jsonurl","errorCode":null,"errorMessage":"state=done but no jsonUrl","messagePattern":"state=done but no jsonUrl","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/paddleocr_vl_cloud_converter.go","lineNumber":238,"sourceCode":"\t\t}\n\n\t\tvar pollResp paddleOCRVLCloudPollResponse\n\t\tif err := json.Unmarshal(respBody, &pollResp); err != nil {\n\t\t\tlogger.Errorf(context.Background(), \"[PaddleOCR-VL Cloud] poll #%d decode error: %v\", pollCount, err)\n\t\t\tsleepCtx(ctx, paddleOCRVLCloudPollInterval)\n\t\t\tcontinue\n\t\t}\n\n\t\tstate := strings.ToLower(pollResp.Data.State)\n\t\tif pollCount == 1 || pollCount%6 == 0 || state == \"done\" || state == \"failed\" {\n\t\t\tlogger.Infof(context.Background(), \"[PaddleOCR-VL Cloud] poll #%d: state=%s pages=%d/%d\",\n\t\t\t\tpollCount, state, pollResp.Data.ExtractProgress.ExtractedPages, pollResp.Data.ExtractProgress.TotalPages)\n\t\t}\n\n\t\tswitch state {\n\t\tcase \"done\":\n\t\t\tif pollResp.Data.ResultURL.JSONURL == \"\" {\n\t\t\t\treturn \"\", fmt.Errorf(\"state=done but no jsonUrl\")\n\t\t\t}\n\t\t\treturn pollResp.Data.ResultURL.JSONURL, nil\n\t\tcase \"failed\":\n\t\t\treturn \"\", fmt.Errorf(\"task failed: %s\", pollResp.Data.ErrorMsg)\n\t\t}\n\n\t\tsleepCtx(ctx, paddleOCRVLCloudPollInterval)\n\t}\n\n\treturn \"\", fmt.Errorf(\"task timed out after %d polls\", pollCount)\n}\n\n// --- result parsing ---\n\ntype paddleOCRVLCloudResultLine struct {\n\tResult struct {\n\t\tLayoutParsingResults []struct {\n\t\t\tMarkdown struct {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/paddleocr_vl_cloud_converter.go#L220-L256","documentation":"pollJob in the PaddleOCR VL cloud converter polls the remote parsing task and, when the API reports state=\"done\", expects a result JSONURL in the response. This error is thrown when the provider marks the task done but omits ResultURL.JSONURL, so the converter has no result to download. It is a defensive guard against a malformed/incomplete provider success response rather than a local bug.","triggerScenarios":"pollJob receives a poll response whose data.state is \"done\" while pollResp.Data.ResultURL.JSONURL is the empty string; called from Read after submitting a document for cloud parsing.","commonSituations":"Provider-side anomalies: the OCR task finished but result generation/upload failed silently; an API version change renamed or moved the result-URL field; a proxy or gateway stripped the JSON body; expired/limited account where the platform drops result artifacts but still flips state to done.","solutions":["Retry Read once or twice — transient provider inconsistency often resolves on a subsequent task submission.","Log the full raw poll response body to confirm the JSONURL field path and whether the API contract changed.","Check the provider's API docs/changelog for a renamed result field (e.g. resultUrl vs resultURL) and update the response struct JSON tags.","Verify the account/plan still has quota and that result artifacts are not being purged by the provider.","If persistent, treat the task as failed and resubmit the document, or fall back to a local parser."],"exampleFix":"// before\nresultURL, err := reader.Read(ctx, file)\nif err != nil {\n    return err // state=done but no jsonUrl\n}\n\n// after\nresultURL, err := reader.Read(ctx, file)\nif err != nil {\n    if strings.Contains(err.Error(), \"no jsonUrl\") {\n        resultURL, err = reader.Read(ctx, file) // retry transient provider glitch\n    }\n    if err != nil {\n        return fmt.Errorf(\"paddleocr cloud parse: %w\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"// before calling Read, ensure the provider result contract is current\n// (field-level validation happens server-side; here we guard the caller)\nif reader == nil {\n    return errors.New(\"paddleocr cloud reader not configured\")\n}","typeGuard":null,"tryCatchPattern":"url, err := reader.Read(ctx, file)\nif err != nil && strings.Contains(err.Error(), \"no jsonUrl\") {\n    url, err = reader.Read(ctx, file) // one retry for provider glitch\n}\nif err != nil { return err }","preventionTips":["Pin and monitor the provider API version; watch changelogs for result-field renames.","Log raw poll responses at debug level to catch contract drift early.","Alert on this error rate — spikes indicate provider-side incidents, not your data.","Keep a local OCR parser as a fallback for critical documents."],"tags":["http-api","ocr","cloud","empty-result"],"backgroundTag":"api-response-missing-field","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}