Skip to content

fix(wake-briefing): scan the shared debug.log once at network scope, not per site - #3524

Open
chubes4 wants to merge 1 commit into
mainfrom
fix/3522-wake-md-network-dedupe
Open

chubes4 wants to merge 1 commit into
mainfrom
fix/3522-wake-md-network-dedupe

Conversation

@chubes4

@chubes4 chubes4 commented Sep 19, 2026

Copy link
Copy Markdown
Member

Fixes #3522

The bug

WordPress multisite shares one wp-content/debug.log file across every site — WP_CONTENT_DIR is network-wide and WakeBriefingTask::resolveDebugLogPath() never reads a per-site option. But gatherNetworkSignals() ran gatherSiteSignals() — which unconditionally called getPhpFatals() — once per blog under switch_to_blog(). On the live 11-site extrachill.com network this meant the identical fatal signature was scanned, parsed, and rendered 11 times, once per site line:

- **extrachill.com** — ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
- **community.extrachill.com** — ⚠ 1 error(s) logged, top signature ×1: "TaskScheduler: queued task requires agent context". · ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
- **shop.extrachill.com** — ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
- **artist.extrachill.com** — ⚠ 1 error(s) logged, top signature ×1: "TaskScheduler: queued task requires agent context". · ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
- **events.extrachill.com** — ⚠ 2 task type(s) failing repeatedly — `unknown` ×128, `extrachill_network_classify_post_terms` ×6. Investigate? · ⚠ 15 job(s) stuck in processing. · ⚠ 342 error(s) logged, top signature ×134: "Job marked as failed". · ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
... (6 more sites, same fatal clause repeated on every line)

~1KB of a 2,389-byte always-on WAKE.md file was one repeated string — roughly 40%. The volume alone is bad; the signal loss is worse: events.extrachill.com genuinely has 342 logged errors, 15 stuck jobs, and 2 task types failing repeatedly (128 + 6 occurrences) — and that line read with the exact same visual weight as the ten duplicate-noise lines around it. The briefing's whole job is a 3-second glance at "anything red that happened recently"; repeating one signature 11× worked directly against that.

Note: the underlying fatal (ec_link_page_owner_compatibility redeclaration) is a separate, already-shipped fix (artist-platform v1.21.1). This PR is only about the briefing reporting one fatal eleven times.

The fix: network-scope scan, not post-hoc dedup

Two approaches were on the table:

  1. Post-hoc dedup — scan per-site as before, then detect signatures identical across all (or most) sites and collapse them.
  2. Network-scope scan — recognize that debug.log is structurally shared, not coincidentally identical, and scan it exactly once at network scope instead of once per site.

Chose (2). A same-vs-different threshold in approach (1) has no meaningful "different" case to guard against — the file is quite literally the same file regardless of which blog is switched-to, so it would always compare identical, on every run, forever. Approach (1) would also still pay the O(sites) cost of reading and parsing an up-to-5MB tail N times, just to discover N identical strings and throw N−1 of them away. Approach (2) fixes both the noise and the wasted I/O: getPhpFatals() now runs once per network briefing, not once per site.

Implementation: gatherSiteSignals() gains a $scan_fatals flag (default true, so single-site-scope behavior — the far more common non-multisite case — is untouched). gatherNetworkSignals() calls getPhpFatals() once before the per-site loop, hoists any result into a **network-wide** line, and passes $scan_fatals = false into each per-blog gatherSiteSignals() call so the identical scan never runs again inside the loop.

Threshold: none needed, by construction — see above. This isn't "hoist when N/N sites agree," it's "this fact was never a per-site fact to begin with."

Placement: the network-wide line renders first, ahead of the per-site lines. Rationale: it's the most network-wide-relevant fact and a reader scanning top-to-bottom should see it before wading into 11 site lines, several of which are otherwise quiet and omitted entirely.

Before / after

Before (11 identical clauses, shown truncated above) — events.extrachill.com's real signal buried under 10 copies of the same noise.

After:

- **network-wide** — ⚠ 9 PHP fatal(s) in debug.log, top: "owner-reference.php: Cannot redeclare function ec_link_page_owner_compatibility…" ×8.
- **community.extrachill.com** — ⚠ 1 error(s) logged, top signature ×1: "TaskScheduler: queued task requires agent context".
- **artist.extrachill.com** — ⚠ 1 error(s) logged, top signature ×1: "TaskScheduler: queued task requires agent context".
- **events.extrachill.com** — ⚠ 2 task type(s) failing repeatedly — `unknown` ×128, `extrachill_network_classify_post_terms` ×6. Investigate? · ⚠ 15 job(s) stuck in processing. · ⚠ 342 error(s) logged, top signature ×134: "Job marked as failed".

One shared fact stated once; events.extrachill.com's real signal now stands on its own with nothing competing for attention on the same line.

Verification

  • php -l on both touched files — clean.
  • No PHPUnit-suite (*Test.php) coverage exists for WakeBriefingTask; it's covered by dependency-free "smoke" scripts under tests/*-smoke.php (existing pattern — see wake-briefing-infra-signals-smoke.php, wake-briefing-credential-integrity-smoke.php). Added tests/wake-briefing-network-fatals-dedupe-smoke.php, run via php tests/wake-briefing-network-fatals-dedupe-smoke.php:
    • Builds a 3-site fixture (two quiet sites + one site with genuinely site-specific facts mirroring events.extrachill.com's shape) and a shared temp debug.log.
    • Asserts gatherNetworkSignals() produces exactly one **network-wide** line, that it renders first, that no per-site line repeats the fatal text, and that the genuinely site-specific facts still render correctly on their own site line.
    • Asserts single-site scope (gatherSiteSignals($since), the default) still includes fatals directly — regression guard for the non-multisite path.
    • 8/8 assertions pass.
  • Re-ran the two pre-existing wake-briefing-*-smoke.php scripts (infra signals: 13/13, credential integrity: 25/25) — both pass unchanged, confirming the $scan_fatals default preserves existing single-site behavior exactly.

Constraints followed

  • Worked only in this worktree, branch fix/3522-wake-md-network-dedupe.
  • No CHANGELOG.md edit, no version bump.
  • No release/deploy.

…not per site

WakeBriefingTask's gatherNetworkSignals() ran gatherSiteSignals() once per
blog under switch_to_blog(), and getPhpFatals() unconditionally scanned
wp-content/debug.log inside it. On an 11-site multisite, debug.log is a
single file shared by every site (WP_CONTENT_DIR and resolveDebugLogPath()
are both network-wide, never per-blog), so an identical fatal signature was
read, parsed, and reported 11 times — once per site line — while re-reading
the same up-to-5MB tail 11 times per run.

Root fix over post-hoc dedup: scan debug.log exactly once in
gatherNetworkSignals(), before the per-site loop, and hoist the result into
a single '**network-wide**' line rendered ahead of the per-site lines.
gatherSiteSignals() gains a $scan_fatals flag (default true, preserving
single-site-scope behavior) so the per-blog loop can opt out instead of
scanning-then-discarding. No per-site fact is lost — the fatal was never a
site-specific fact — and per-site lines keep their genuinely per-site
signals (job failures, stuck jobs, grouped errors) uncollapsed.

Adds a dedicated smoke test exercising gatherNetworkSignals() against a
3-site fixture: one hoisted network-wide line, no per-site repetition, and
a site with real facts (mirroring the events.extrachill.com signal-loss
example from the issue) still rendering on its own line.

Fixes #3522
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.

WAKE.md repeats one network-wide log signature once per site (11x on an 11-site network)

1 participant