isTaskComplete checks is_sent=3, which nothing writes; ProcessTask discards its error (742fa834)

Author: runaway518Created Sep 2, 2026Updated Sep 2, 2026

Found while evaluating dev commits for a downstream fork. Two issues in 742fa834 ("Fix campaigns stuck in Processing state"), both in core/internal/service/batch_mail/task_executor.go.

1. is_sent = 3 is never written, so the isTaskComplete change is dead logic

The commit changes the completion query to:

go
Fields("COUNT(1) as total_count, SUM(CASE WHEN is_sent IN (1, 3) THEN 1 ELSE 0 END) as sent_count")

with the stated rationale "isTaskComplete counts failed recipients (is_sent=3) as done".

Nothing in the tree writes 3. The writers are:

  • 0batch_mail.go:234, batch_mail.go:282
  • 2task_executor.go:692 (claimed for sending)
  • 1task_executor.go:934 (success) and task_executor.go:971 (failure)

The schema documents only three states (database_initialization/batch_mail.go:104):

is_sent SMALLINT DEFAULT 0, -- 0: Pending processing 2: Extracted and ready for transmission 1: Transmission completed

Failures are already recorded as 1, deliberately — task_executor.go:967 carries the comment // 失败的也更新 is_sent 和 sent_time 避免卡住发送状态.

So IN (1, 3) is currently a no-op superset of = 1. It is harmless today, but it encodes an assumption the schema contradicts: if a migration later introduces 3 with different semantics, task completion changes silently.

Either 3 should be introduced as a real failed state (and the schema comment updated), or the clause should stay = 1.

2. ProcessTask now discards the processing error

go
if err := e.processTaskRecipients(ctx, task, emailContent); err != nil {
    g.Log().Error(ctx, "failed to process task: %v", err)
    if errors.Is(err, context.Canceled) {
        return nil
    }
    // Don't return — fall through to check completion
}

err is scoped to the if statement, so after logging it is dropped. Callers — task_executor.go:130 and controller/batch_mail/batch_mail_v1_resume_task.go:69 — can no longer distinguish a partially failed run from a clean one.

Falling through to the completion check is reasonable and does fix the stuck-in-Processing symptom (partial failure leaves rows at 0, so the task correctly stays incomplete). The concern is only that the error is swallowed rather than propagated after the completion check.