Tencent/WeKnora · warning

failed to unmarshal FAQ import progress: %w

Error message

failed to unmarshal FAQ import progress: %w

What it means

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.

Source

Thrown at internal/application/service/knowledge_faq_import.go:2776

func (s *knowledgeService) GetFAQImportProgress(ctx context.Context, taskID string) (*types.FAQImportProgress, error) {
	if s.redisClient == nil {
		if v, ok := s.memFAQProgress.Load(taskID); ok {
			return v.(*types.FAQImportProgress), nil
		}
		return nil, werrors.NewNotFoundError("FAQ import task not found")
	}
	key := getFAQImportProgressKey(taskID)
	data, err := s.redisClient.Get(ctx, key).Bytes()
	if err != nil {
		if errors.Is(err, redis.Nil) {
			return nil, werrors.NewNotFoundError("FAQ import task not found")
		}
		return nil, fmt.Errorf("failed to get FAQ import progress from Redis: %w", err)
	}

	var progress types.FAQImportProgress
	if err := json.Unmarshal(data, &progress); err != nil {
		return nil, fmt.Errorf("failed to unmarshal FAQ import progress: %w", err)
	}

	// If task is completed, enrich with persisted result fields from database
	if progress.Status == types.FAQImportStatusCompleted && progress.KnowledgeID != "" {
		tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
		knowledge, err := s.repo.GetKnowledgeByID(ctx, tenantID, progress.KnowledgeID)
		if err == nil && knowledge != nil {
			if result, err := knowledge.GetLastFAQImportResult(); err == nil && result != nil {
				progress.SuccessCount = result.SuccessCount
				progress.FailedCount = result.FailedCount
				progress.PartialFailedCount = result.PartialFailedCount
				progress.SkippedCount = result.SkippedCount
				progress.MergedCount = result.MergedCount
				progress.AddedCount = result.AddedCount
				progress.ImportMode = result.ImportMode
				progress.ImportedAt = result.ImportedAt
				progress.DisplayStatus = result.DisplayStatus
				progress.ProcessingTime = result.ProcessingTime

View on GitHub (pinned to 988cbb0330)

Solutions

  1. DEL the stale progress key for the taskID and re-run the import
  2. Make unmarshaling tolerant: version the progress payload or ignore unknown fields (json.Decoder with DisallowUnknownFields removed)
  3. On unmarshal failure, fall back to NotFoundError so polling clients see a clean state
  4. Purge FAQ import progress keys after schema-changing deployments

Example fix

// before
var progress types.FAQImportProgress
if err := json.Unmarshal(data, &progress); err != nil {
	return nil, fmt.Errorf("failed to unmarshal FAQ import progress: %w", err)
}
// after
var progress types.FAQImportProgress
if err := json.Unmarshal(data, &progress); err != nil {
	logger.Errorf(ctx, "corrupt FAQ import progress for task %s, resetting: %v", taskID, err)
	_ = s.redisClient.Del(ctx, key)
	return nil, werrors.NewNotFoundError("FAQ import task not found")
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Reader-side sanity check before unmarshaling
data, err := redisClient.Get(ctx, key).Bytes()
if err == nil {
	if !json.Valid(data) {
		redisClient.Del(ctx, key) // drop corrupt/stale entry
		return werrors.NewNotFoundError("FAQ import task not found")
	}
}

Type guard

func validFAQProgress(data []byte) (*types.FAQImportProgress, bool) {
	var p types.FAQImportProgress
	if err := json.Unmarshal(data, &p); err != nil || p.Status == "" {
		return nil, false
	}
	return &p, true
}

Try / catch

progress, err := svc.GetFAQImportProgress(ctx, taskID)
if err != nil {
	if strings.Contains(err.Error(), "failed to unmarshal FAQ import progress") {
		// stale schema from an older deployment: treat as no task
		return nil, statusNotFound("FAQ import task not found (stale progress data)")
	}
	return nil, err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/2ba3729749b57b5e. Report an issue: GitHub.