Skip to content

fix: match compound failed statuses in jobs cleanup and retention#2861

Merged
chubes4 merged 1 commit into
mainfrom
fix-jobs-cleanup-status-variants
Jul 8, 2026
Merged

fix: match compound failed statuses in jobs cleanup and retention#2861
chubes4 merged 1 commit into
mainfrom
fix-jobs-cleanup-status-variants

Conversation

@chubes4

@chubes4 chubes4 commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

wp datamachine jobs cleanup --status=failed was a permanent no-op because STATUS_VARIANTS mapped 'failed' to an IN ('failed') list, but real failure statuses are always compound (failed - packet_failure, failed: Daily memory completion policy was not satisfied...) and embed arbitrary error text that can never be enumerated. resolve_status_match() preferred the exact IN (...) list for known keys, so neither count_old_jobs() nor delete_old_jobs() matched any compound failure — contradicting the documented contract on delete_old_jobs() ("Uses LIKE matching to handle compound statuses (e.g., 'failed - timeout')") and the LIKE semantics used everywhere else in the file.

Fixes #2858.

Root cause

inc/Core/Database/Jobs/Jobs.php:

private const STATUS_VARIANTS = array(
    'completed' => array( 'completed', 'completed_no_items', 'agent_skipped' ),
    'failed'    => array( 'failed' ),   // ← only matches the exact string 'failed'
);

resolve_status_match() returns the IN (...) variant list when the prefix is a known key, only falling back to LIKE 'prefix%' for unknown prefixes. Since 'failed' is a known key, every compound failure is invisible. Production evidence from the issue: 194 failed rows, zero with the exact status failed.

This affected both consumers of resolve_status_match():

  • CLI: JobsCommand::cleanupcount_old_jobs() / delete_old_jobs('failed') (the reported bug).
  • Retention: RetentionFailedJobsTaskRetentionCleanup::cleanupFailedJobs()delete_old_jobs('failed')shares the exact same bug, so automated retention never purged failed rows either (failed counts grew unboundedly on health dashboards).

The fix

Drop 'failed' from STATUS_VARIANTS so resolve_status_match('failed') falls through to the LIKE 'failed%' branch.

LIKE is the honest match for failures because failure statuses embed arbitrary error text and can never be enumerated. This approach was chosen over the alternatives for these reasons:

  • Index-friendly. LIKE 'failed%' is a prefix pattern, so MySQL can still use the composite idx_status_created (status(50), created_at) index for a range scan — the optimization the constant was introduced to preserve.
  • Matches bare failed too. A prefix LIKE 'failed%' matches the bare string failed (zero trailing chars) as well as every compound form, so a hypothetical bare-failed row is still counted/deleted.
  • Consistent with the rest of the file. get_jobs_count(), get_jobs_for_list_table(), build_jobs_summary_where(), and delete_jobs() already use status LIKE 'failed%' for the failed filter. The IN ('failed') list was the outlier that broke the documented contract.
  • Keeps the completed optimization intact. completed variants (completed, completed_no_items, agent_skipped) are genuinely enumerable, so the IN (...) optimization stays for that key.

The completed retention/count paths (RetentionCleanup::cleanupCompletedJobs / countCompletedJobs) are unchanged and verified by tests.

Retention-task finding

RetentionFailedJobsTaskRetentionCleanup::cleanupFailedJobs() (inc/Engine/AI/System/Tasks/Retention/RetentionCleanup.php:434) calls (new Jobs())->delete_old_jobs('failed', $max_age_days ) — the same broken path. No separate fix is needed: fixing STATUS_VARIANTS fixes both the CLI and the automated retention task in one place. countFailedJobs() (line 430) is fixed by the same change.

Tests

New tests/jobs-cleanup-status-variants-smoke.php stubs $wpdb with an in-memory jobs table and exercises count_old_jobs() / delete_old_jobs() against representative rows mirroring the production evidence (compound failures, bare failed, completed variants, a recent failed row, a non-terminal processing row). 16 assertions cover:

  • compound statuses (failed - x, failed: y) are counted and deleted
  • bare failed still matches
  • the generated SQL uses LIKE 'failed%' (not IN ('failed')) for the failed prefix
  • the age cutoff is applied and protects recent failed jobs from deletion
  • non-terminal (processing) and completed-variant rows are untouched by a failed cleanup pass
  • completed path keeps the IN ('completed','completed_no_items','agent_skipped') optimization and matches its 3 variants
  • completed delete leaves failed rows untouched
  • empty/invalid inputs are rejected without touching rows

Verification

  • New smoke test: 16/16 PASS (php tests/jobs-cleanup-status-variants-smoke.php).
  • homeboy review data-machine lint --changed-since origin/main: PASS (phpcs + phpstan clean).
  • No regressions in jobs-related smoke tests (jobs-list-efficiency, jobs-get-children, jobs-command-undo-fanout, jobs-trim-runtime-queues all pass).
  • CI on this PR is the real gate.

Constraints honored

  • No release, no deploy, no merge — PR only.
  • No CHANGELOG.md edit, no version bump (homeboy owns both).
  • Conventional commit (fix:).
  • No Extra Chill / site-specific references in code or tests.

STATUS_VARIANTS mapped 'failed' to an IN ('failed') list, but real
failure statuses are always compound (failed - packet_failure,
failed: policy not satisfied...) and embed arbitrary error text that
can never be enumerated. resolve_status_match() preferred the IN list
for known keys, so count_old_jobs()/delete_old_jobs() matched nothing
for 'failed' — making
a permanent no-op and breaking RetentionFailedJobsTask (same path).

Drop 'failed' from STATUS_VARIANTS so it falls through to the prefix
LIKE 'failed%' branch. LIKE is the honest match for failures: it still
uses idx_status_created (prefix pattern), matches bare 'failed' and
every compound form, and aligns with the LIKE semantics already used
by get_jobs_count(), get_jobs_for_list_table(), and delete_jobs().
The completed IN-optimization is preserved (its variants are genuinely
enumerable).

Fixes #2858
@homeboy-ci

homeboy-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Homeboy Results — data-machine

Lint

review lint — passed

ℹ️ Full options: homeboy self docs commands/lint
Deep dive: homeboy review lint data-machine --changed-since c174857

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-review-lint-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-review-lint-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28948634151

Test

review test — passed

ℹ️ Auto-fix lint issues: homeboy refactor data-machine --from lint --write
ℹ️ Collect coverage: homeboy test data-machine --coverage
ℹ️ Pass args to test runner: homeboy test -- [args]
ℹ️ Full options: homeboy self docs commands/test
Deep dive: homeboy review test data-machine --changed-since c174857

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-review-test-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-review-test-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28948634151

Audit

review audit — passed

  • audit — 96 finding(s)
  • Total: 96 finding(s)

Deep dive: homeboy review audit data-machine --changed-since c174857

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-review-audit-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-review-audit-quality-Linux-node24 contains exported Homeboy run history for deeper queries.
  • Drill-down: download the observation artifact, then run homeboy runs import <dir>, homeboy runs list, and homeboy runs findings <run-id>.
  • Artifacts are attached to the workflow run: https://github.com/Extra-Chill/data-machine/actions/runs/28948634151
Tooling versions
  • Homeboy CLI: homeboy 0.281.20+123145ee082b+83744685
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: f89b4297
  • Action: unknown@unknown

@chubes4 chubes4 merged commit 7c19153 into main Jul 8, 2026
5 checks passed
@chubes4 chubes4 deleted the fix-jobs-cleanup-status-variants branch July 8, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

jobs cleanup never matches compound failed statuses (STATUS_VARIANTS breaks documented prefix matching)

1 participant