Skip to content

fix: unconditionally top up agents-api substrate to prevent hasher fatal (#2500)#2503

Merged
chubes4 merged 2 commits into
mainfrom
fix-agent-hasher-fatal
Jun 4, 2026
Merged

fix: unconditionally top up agents-api substrate to prevent hasher fatal (#2500)#2503
chubes4 merged 2 commits into
mainfrom
fix-agent-hasher-fatal

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented Jun 4, 2026

Summary

Fixes the production fatal Uncaught Error: Class "WP_Agent_Package_Artifact_Hasher" not found in inc/Engine/Bundle/AgentBundleArtifactHasher.php:24, observed on extrachill.com (03-Jun-2026, tied to agent bundle ops / agent export).

Closes #2500.

Root cause

The agents-api substrate's bootstrap (vendor/.../agents-api/agents-api.php) loads all its WP_Agent_* class files inside a single block guarded by:

if ( defined( 'AGENTS_API_LOADED' ) ) { return; }
define( 'AGENTS_API_LOADED', true );
// ... require_once every src/ class file (including the hasher) ...

That guard is all-or-nothing per-constant. Any condition that leaves AGENTS_API_LOADED defined while the per-class require_once list did not run to completion for the request leaves the WP_Agent_* classes missing. The first time a DM namespaced delegator (DataMachine\Engine\Bundle\AgentBundleArtifactHasher) references the global \WP_Agent_Package_Artifact_Hasher, PHP fatals with the bare "Class not found".

Triggers in this class of failure:

  • Version skew — two copies of the substrate coexist (DM's vendored copy + a separately activated standalone agents-api plugin) and the older copy wins the load race, early-returning the newer copy before it requires its classes.
  • Partial / aborted bootstrap — the constant is defined near the top of the bootstrap, so an interruption leaves the constant set but the classes unloaded.
  • Load-order regression — any future path that references a WP_Agent_* global before the bootstrap require block completes.

The prior guardrail (#2485) only topped up the substrate when a skew was detected (AGENTS_API_LOADED defined AND canary class missing). That gated recovery behind one specific sub-case, so the broader "constant defined, classes incomplete" failure was still exposed. (Production was additionally running 0.138.2, which predates #2485 entirely.)

The fix

datamachine_agents_api_run_guardrail() now tops up the bundled substrate class set unconditionally whenever AGENTS_API_LOADED is defined, instead of only when skew is pre-detected:

  • Every bundled include is require_once, so the top-up is a no-op in the common single-copy case and a complete recovery when the class set is incomplete.
  • This closes the entire "constant defined, classes missing" fatal class, not just the dual-load skew sub-case.
  • If the canary class is still missing after the top-up (e.g. a bundled file is genuinely absent), it surfaces a clear admin_notices error and a hard WP_CLI::error — loud and actionable instead of a cryptic fatal.

Docblocks and the operator-facing message were broadened to reflect the wider set of causes.

Verification

  • New pure-PHP smoke assertion in tests/agents-api-dualload-guardrail-smoke.php exercises the exact bug: WP_Agent_Package_Artifact_Hasher not found — fatal in AgentBundleArtifactHasher #2500 regression path: with AGENTS_API_LOADED defined and the hasher class absent, the guardrail tops up the substrate and DataMachine\Engine\Bundle\AgentBundleArtifactHasher::hash() returns a valid sha256 without fataling.
  • Full smoke suite: all 11 assertions pass (php tests/agents-api-dualload-guardrail-smoke.php).
  • A standalone reproduction harness (simulating the precise prod state: constant defined + hasher class missing) confirmed: before fix → fatal; after fix → hasher loaded, delegator returns a 64-char sha256, second guardrail run is an idempotent no-op.
  • php -l clean on both changed files.
  • phpcs exit 0 on both changed files (the 31 warnings homeboy's --changed-since reports are pre-existing alignment warnings in ChatOrchestrator.php / AgentsChatHandler.php, which this PR does not touch).

Layer purity

The fix lives in DM's own bundle/bootstrap layer (inc/agents-api-guardrail.php), which owns the WP_Agent_* delegators — no higher layer involved. The durable fix still belongs upstream in Automattic/agents-api (idempotent per-class loading independent of the bootstrap constant); this guardrail is DM's defensive recovery until that lands.

The agents-api bootstrap loads its WP_Agent_* class files only inside an
AGENTS_API_LOADED-guarded block that early-returns. Any condition that defines
the constant without completing the require block (version skew, partial/aborted
bootstrap, or a load-order regression) leaves the classes missing and produces a
bare "Class WP_Agent_Package_Artifact_Hasher not found" fatal the first time a
namespaced delegator references the global -- e.g. during agent export.

The previous guardrail only topped up the substrate when a dual-load *skew* was
detected (canary class missing AND constant defined). That gated recovery behind
one specific sub-case. This makes the bundled class top-up run UNCONDITIONALLY
whenever AGENTS_API_LOADED is defined: every include is require_once, so it is a
no-op in the common single-copy case and a complete recovery whenever the class
set is incomplete -- closing the entire "constant defined, classes missing"
fatal class rather than just the skew sub-case. If the canary is still missing
after the top-up (bundled file absent), it fails loud via admin notice / WP-CLI
error instead of a cryptic fatal.

Adds a smoke-test assertion exercising the exact #2500 regression path
(AgentBundleArtifactHasher::hash() delegating to the topped-up global).

Closes #2500.
@homeboy-ci
Copy link
Copy Markdown
Contributor

homeboy-ci Bot commented Jun 4, 2026

Homeboy Results — data-machine

Lint

lint — passed

ℹ️ Full options: homeboy docs commands/lint
Deep dive: homeboy lint data-machine --changed-since 2d45e05

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-lint-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-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/26956101607

Test

test — passed

  • 1289 passed
  • 4 skipped

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

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-test-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-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/26956101607

Audit

audit — passed

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

Deep dive: homeboy audit data-machine --changed-since 2d45e05

Artifacts and drill-down
  • CI results artifact: homeboy-ci-results-data-machine-audit-quality-Linux-node24 contains immediate command JSON for this action invocation.
  • Observation artifact: homeboy-observations-data-machine-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/26956101607
Tooling versions
  • Homeboy CLI: homeboy 0.220.3+1dd2f967
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: e42a1d7d
  • Action: unknown@unknown

Self-review of the prior commit found two issues:

1. The unconditional top-up parsed the bootstrap and ran ~185 require_once
   on every network request (~0.25ms each) even on the healthy path. The
   #2500 fatal was the canary class (WP_Agent_Package_Artifact_Hasher) itself
   missing, which the cheap class_exists() canary gate already detects — so
   restore that gate. The guardrail now adds zero file I/O on the common
   healthy path and only tops up when the canary is actually absent.

2. The self_heal docblock falsely claimed "side-effect add_action
   registrations are intentionally not re-fired here." The parsed require list
   DOES include 21 register-*.php files that call add_action() at file scope.
   They are re-run, but require_once dedup means already-loaded ones are
   no-ops (no double-registration), and ones the incomplete bootstrap skipped
   are correctly executed. Docblocks corrected to describe the real behavior.

No behavior change for the #2500 recovery path; the smoke test (11 assertions,
including the delegator regression) still passes.
@chubes4 chubes4 merged commit 0cf338e into main Jun 4, 2026
5 checks passed
@chubes4 chubes4 deleted the fix-agent-hasher-fatal branch June 4, 2026 14:38
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.

bug: WP_Agent_Package_Artifact_Hasher not found — fatal in AgentBundleArtifactHasher

1 participant