{"record":{"id":"2ba3729749b57b5e","repo":"Tencent/WeKnora","slug":"failed-to-unmarshal-faq-import-progress-w","errorCode":null,"errorMessage":"failed to unmarshal FAQ import progress: %w","messagePattern":"failed to unmarshal FAQ import progress: %w","errorType":"exception","errorClass":null,"httpStatus":500,"severity":"warning","filePath":"internal/application/service/knowledge_faq_import.go","lineNumber":2776,"sourceCode":"func (s *knowledgeService) GetFAQImportProgress(ctx context.Context, taskID string) (*types.FAQImportProgress, error) {\n\tif s.redisClient == nil {\n\t\tif v, ok := s.memFAQProgress.Load(taskID); ok {\n\t\t\treturn v.(*types.FAQImportProgress), nil\n\t\t}\n\t\treturn nil, werrors.NewNotFoundError(\"FAQ import task not found\")\n\t}\n\tkey := getFAQImportProgressKey(taskID)\n\tdata, err := s.redisClient.Get(ctx, key).Bytes()\n\tif err != nil {\n\t\tif errors.Is(err, redis.Nil) {\n\t\t\treturn nil, werrors.NewNotFoundError(\"FAQ import task not found\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to get FAQ import progress from Redis: %w\", err)\n\t}\n\n\tvar progress types.FAQImportProgress\n\tif err := json.Unmarshal(data, &progress); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal FAQ import progress: %w\", err)\n\t}\n\n\t// If task is completed, enrich with persisted result fields from database\n\tif progress.Status == types.FAQImportStatusCompleted && progress.KnowledgeID != \"\" {\n\t\ttenantID := ctx.Value(types.TenantIDContextKey).(uint64)\n\t\tknowledge, err := s.repo.GetKnowledgeByID(ctx, tenantID, progress.KnowledgeID)\n\t\tif err == nil && knowledge != nil {\n\t\t\tif result, err := knowledge.GetLastFAQImportResult(); err == nil && result != nil {\n\t\t\t\tprogress.SuccessCount = result.SuccessCount\n\t\t\t\tprogress.FailedCount = result.FailedCount\n\t\t\t\tprogress.PartialFailedCount = result.PartialFailedCount\n\t\t\t\tprogress.SkippedCount = result.SkippedCount\n\t\t\t\tprogress.MergedCount = result.MergedCount\n\t\t\t\tprogress.AddedCount = result.AddedCount\n\t\t\t\tprogress.ImportMode = result.ImportMode\n\t\t\t\tprogress.ImportedAt = result.ImportedAt\n\t\t\t\tprogress.DisplayStatus = result.DisplayStatus\n\t\t\t\tprogress.ProcessingTime = result.ProcessingTime","sourceCodeStart":2758,"sourceCodeEnd":2794,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/knowledge_faq_import.go#L2758-L2794","documentation":"GetFAQImportProgress wraps json.Unmarshal failures when deserializing the FAQImportProgress document stored in Redis. It means the stored bytes are not valid JSON for the current FAQImportProgress shape — typically stale data written by an older code version whose schema has since changed, or corrupted data.","triggerScenarios":"A progress document written by a previous deployment (renamed/retyped fields, status strings, or KnowledgeID types) is read by current code; manual Redis edits; truncated/corrupt values from memory pressure or non-JSON writes.","commonSituations":"Deploying a new version that changed FAQImportProgress fields while old tasks are still in Redis; shared Redis across environments with different schema versions; someone debugging by writing raw values to the key.","solutions":["DEL the stale progress key for the taskID and re-run the import","Make unmarshaling tolerant: version the progress payload or ignore unknown fields (json.Decoder with DisallowUnknownFields removed)","On unmarshal failure, fall back to NotFoundError so polling clients see a clean state","Purge FAQ import progress keys after schema-changing deployments"],"exampleFix":"// before\nvar progress types.FAQImportProgress\nif err := json.Unmarshal(data, &progress); err != nil {\n\treturn nil, fmt.Errorf(\"failed to unmarshal FAQ import progress: %w\", err)\n}\n// after\nvar progress types.FAQImportProgress\nif err := json.Unmarshal(data, &progress); err != nil {\n\tlogger.Errorf(ctx, \"corrupt FAQ import progress for task %s, resetting: %v\", taskID, err)\n\t_ = s.redisClient.Del(ctx, key)\n\treturn nil, werrors.NewNotFoundError(\"FAQ import task not found\")\n}","handlingStrategy":"type-guard","validationCode":"// Reader-side sanity check before unmarshaling\ndata, err := redisClient.Get(ctx, key).Bytes()\nif err == nil {\n\tif !json.Valid(data) {\n\t\tredisClient.Del(ctx, key) // drop corrupt/stale entry\n\t\treturn werrors.NewNotFoundError(\"FAQ import task not found\")\n\t}\n}","typeGuard":"func validFAQProgress(data []byte) (*types.FAQImportProgress, bool) {\n\tvar p types.FAQImportProgress\n\tif err := json.Unmarshal(data, &p); err != nil || p.Status == \"\" {\n\t\treturn nil, false\n\t}\n\treturn &p, true\n}","tryCatchPattern":"progress, err := svc.GetFAQImportProgress(ctx, taskID)\nif err != nil {\n\tif strings.Contains(err.Error(), \"failed to unmarshal FAQ import progress\") {\n\t\t// stale schema from an older deployment: treat as no task\n\t\treturn nil, statusNotFound(\"FAQ import task not found (stale progress data)\")\n\t}\n\treturn nil, err\n}","preventionTips":["Version the FAQImportProgress JSON (add a schemaVersion field) and migrate or discard old versions on deploy","Purge FAQ import progress keys during schema-changing releases","Keep FAQImportProgress fields backward-compatible (add, don't rename/retype)","Treat unmarshal failures as stale data and fall back to NotFoundError rather than 500"],"tags":["redis","json","faq-import","schema-migration"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}