You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wavemachine v2 (commit 37926b2, 2026-04-22) replaced the auto-updating Discord embed card with plain-text event announcements. Both forms are valuable and were never meant to be alternatives — the v1→v2 rewrite silently dropped the embed invocation while keeping the plain-text messages. The embed-posting script (scripts/discord-status-post) is still present, still functional, and still referenced by the v2 /wavemachine skill body at line 99 ("If you want live-updating telemetry, look at scripts/discord-status-post") — but it is never actually invoked.
Environment
Where observed: Discord #wave-status channel (1487386934094462986)
Version/commit:/wavemachine skill body v2 (commit 37926b2 and later — currently at f6d21bf on main)
Frequency: consistent — every /wavemachine run since v2 shipped has been missing the embed card
Regression Window
Last embed card received: 2026-04-20T03:12 (grimoire's last pre-v2 /wavemachine run on cc-workflow)
Wavemachine v2 shipped: 2026-04-23
3-day gap between last-known-good and regression cause
Steps to Reproduce
Run /wavemachine against any approved multi-wave plan.
Watch #wave-status in Discord during execution.
Observe: plain-text announcements fire on start, wave complete, wavemachine complete, abort.
Observe: no auto-updating embed card with Phase/Wave/Action/Flight/Progress/Deferrals fields appears; no pinned message PATCHes in place.
Expected Behavior
BOTH outputs appear:
Plain-text event announcements (current) — the timeline. 🏄 Wave N started, ✅ Wave N complete, 🌊 Wavemachine complete, etc. These are the "what just happened" feed.
Auto-updating embed card (regressed) — the dashboard. A single pinned message, PATCH-updated in place as wave state changes. Shows Phase (e.g. 1/2 — Foundation), Wave (2/2 in phase 1), Action (waiting-on-meatbag — Wave 1 complete), Flight (current/total), Progress bar (20-char Unicode with percentage), Deferrals (pending/accepted).
They answer different questions — timeline vs. dashboard — and should coexist.
Reference screenshots attached by BJ on the original chat that surfaced this bug.
Actual Behavior
Only plain-text announcements fire. Embed card never appears. Message ID cache at .claude/status/discord-status.json remains empty (no message to PATCH).
Root Cause
From Explore-agent investigation (2026-04-26):
scripts/discord-status-post is fully functional, untouched since commit 8da9c9f (2026-02-04). Has debounce (15s, last-write-wins), dev-team fallback, ID caching, PATCH-in-place update logic.
v1 wavemachine (pre-37926b2) implicitly relied on this script being invoked on state changes.
v2 wavemachine rewrite (37926b2) restructured the loop into the top-level Orchestrator and added plain-text event announcements via mcp__disc-server__disc_send, but never added a callsite for discord-status-post.
v2 skill body (~/.claude/skills/wavemachine/SKILL.md) line 99 acknowledges the script exists but treats it as out-of-scope: "If you want live-updating telemetry, look at scripts/discord-status-post (the embed it posts to #wave-status is PATCHed in place on every call) or the Discord notifications..."
Same miss in v2 /nextwave skill body — no callsite.
Classic left-hand / right-hand: the script is there, the skill body references it as a capability, but no code path actually calls it.
Severity
severity::minor — functional regression, not a blocker. Plain-text messages still convey the important state transitions; the embed card was dashboard polish. Restoring it is straightforward (one callsite).
Debounce is already in the script (15s window); rapid state changes don't cause spam.
In /nextwave (after each wave_flight_done() for per-flight granularity — matches the original v1 "live-updating" contract):
Same invocation.
Why not auto-wire from sdlc-server instead: this is a terminal-side UX tool; keeping it out of MCP handlers (which are platform-neutral) is cleaner. Skill-body invocation is the right layer.
Acceptance Criteria
Running /wavemachine on any multi-wave plan produces an embed card in #wave-status within 30 seconds of the first state change
The embed card PATCH-updates in place as state advances (does NOT spam new messages)
Plain-text event announcements (🏄 Wave N started, ✅ Wave N complete, etc.) continue to fire — both outputs coexist
Embed card shows correct Phase/Wave/Action/Flight/Progress/Deferrals fields derived from .claude/status/state.json, phases-waves.json, and flights.json
Debounce works — rapid state changes (e.g. multiple flight completions in a tight window) result in a single embed update, not a flood
Cleanup: .claude/status/discord-status.json message-ID cache is populated after first post; subsequent posts PATCH the cached ID
/nextwave per-flight updates land on the same pinned message (no duplicate pinned messages per wave)
Not In Scope
Do not replace plain-text announcements. Both outputs are wanted.
Do not refactor scripts/discord-status-post. It works; just call it.
Do not migrate to an MCP-handler-based posting path. The script's debounce + ID cache live on the client side where they belong.
Do not introduce a background-process/daemon version (Explore's Option C). The skill-body-invocation-at-state-changes approach (Option A) is sufficient and has no long-running-process complexity.
Dependencies
None beyond the existing scripts/discord-status-post (present) and the skill body files under ~/.claude/skills/.
Implementation Steps
All changes are skill-body edits — no new code, no new scripts, no MCP changes.
1. Add the embed callsite to /wavemachine — Launch Sequence
In ~/.claude/skills/wavemachine/SKILL.md, Launch Sequence step 5 (currently line ~84) posts the plain-text disc_send for wavemachine start. Immediately after that disc_send call, add:
Fire-and-forget: ./scripts/discord-status-post --channel-id 1487386934094462986 --state-dir .claude/status
(dev-team is auto-resolved from the agent identity file; --dev-team flag is optional)
This posts the first embed card as soon as state.json is initialized, giving #wave-status watchers a dashboard within seconds of autopilot launch. The script exits cleanly if state.json is absent (so if the call races initialization, it is a silent no-op).
2. Add the embed callsite to /wavemachine — Loop body, post-wave_complete
In ~/.claude/skills/wavemachine/SKILL.md, The Loop step 4, "OK" branch (currently line ~153): after the existing generate-status-panel fire-and-forget call, add:
This is the highest-value callsite — it fires once per completed wave, after state.json already reflects the wave's done action and merged-MR metadata. The 15s debounce in the script means that multiple rapid wave completions (unlikely but possible on very fast waves) collapse into a single embed update rather than a flood.
3. Add the embed callsite to /wavemachine — Every terminal exit
In ~/.claude/skills/wavemachine/SKILL.md, Announcements section — the terminal events each already call generate-status-panel synchronously before the disc_send. Add the embed call immediately after generate-status-panel and before disc_send at each of the five terminal events:
On clean completion (wave_next_pending() returned null) — line ~306 area
On gate-blocked completion (trust-score gate any-red) — line ~313 area
On circuit-breaker trip — line ~320 area
On per-wave BLOCKED or FAIL — line ~327 area
On user interrupt (Interrupt Handling section) — line ~278 area
In every case the invocation is the same fire-and-forget call. The embed captures the terminal state (the status panel was just regenerated synchronously, so state.json is current); the debounce timer fires 15 seconds later and posts the final embed — the operator sees the closing-frame dashboard in Discord alongside the terminal plain-text announcement.
4. Add the embed callsite to /nextwave — after wave_flight_done
In ~/.claude/skills/nextwave/SKILL.md, Prime(post-flight) prompt template, step 5 (line ~327): the step currently reads "Call wave_flight_done(M) after all merges land." Add immediately after that:
This is the per-flight granularity callsite — it fires once per flight merge batch, after wave_flight_done has updated flights.json with the completed flight's status. The embed card's Flight field updates live as flights land, which was the original v1 "live-updating" contract.
5. State file note — message-ID cache
The script maintains .claude/status/discord-status.json as a message-ID cache for PATCH-in-place behavior. This file does not need to be pre-created — the script creates it on first POST. It should be added to .gitignore (or the existing **/.claude/status/ gitignore entry already covers it — verify). The cache survives wavemachine restarts, so a crashed-and-resumed session will PATCH the same pinned message rather than posting a new one.
6. Update the reference text in /wavemachine SKILL.md
In the Status Panel Lifecycle section (currently line ~99-100), the text reads: "If you want live-updating telemetry, look at scripts/discord-status-post (the embed it posts to #wave-status is PATCHed in place on every call) or the Discord notifications..."
Update this sentence to reflect that the script is now invoked (not just referenced): "The embed card is posted and PATCHed in place via scripts/discord-status-post, which fires fire-and-forget at each state-change lifecycle event (wavemachine start, per-wave wave_complete, per-flight wave_flight_done, and every terminal exit)."
Test Procedures
This fix is entirely in skill-body prose (SKILL.md files) — there is no automated test suite for skill bodies. Verification is manual, using a real or minimal test campaign.
Manual verification procedure
Precondition: Confirm scripts/discord-status-post is on PATH or reachable as ./scripts/discord-status-post from the repo root. Run python3 scripts/discord-status-post --help — expect the usage block to print cleanly.
Confirm bot token: Verify ~/secrets/discord-bot-token exists (or DISCORD_BOT_TOKEN is set). The script prints a clear error and exits 1 if missing — confirm this before running a campaign.
Run a minimal test campaign: Use any approved 1-wave plan (even a single-issue wave on a throwaway branch). Invoke /wavemachine.
As each flight lands (wave_flight_done): the embed card PATCHes in place — same message, updated Flight field. No new message is posted. This verifies callsite feat: add sync.sh for local-to-repo reverse sync #4 (/nextwave Prime(post-flight) step 5).
Confirm PATCH-in-place (no duplicates): Count messages from the bot in #wave-status across the full campaign — there should be exactly ONE embed message, updated in place, regardless of how many waves or flights ran.
Confirm plain-text coexistence: Plain-text announcements (disc_send calls) must still fire alongside the embed updates — verify both appear in channel.
Confirm .claude/status/discord-status.json is populated: After the first embed post, cat .claude/status/discord-status.json should show channel_id, message_id, and updated_at fields.
Grep verification (post-edit): After editing the SKILL.md files, confirm callsites are present:
Expect non-zero counts on both. (Pre-edit, wavemachine returns 1 — the reference-only line — and nextwave returns 0.)
Unit tests
N/A — skill bodies have no automated test suite. The script itself (scripts/discord-status-post) has no test harness in this repo; its correctness is established by the functional verification above and by the fact that it is unchanged since commit 8da9c9f.
Summary
Wavemachine v2 (commit
37926b2, 2026-04-22) replaced the auto-updating Discord embed card with plain-text event announcements. Both forms are valuable and were never meant to be alternatives — the v1→v2 rewrite silently dropped the embed invocation while keeping the plain-text messages. The embed-posting script (scripts/discord-status-post) is still present, still functional, and still referenced by the v2/wavemachineskill body at line 99 ("If you want live-updating telemetry, look at scripts/discord-status-post") — but it is never actually invoked.Environment
#wave-statuschannel (1487386934094462986)/wavemachineskill body v2 (commit37926b2and later — currently atf6d21bfon main)/wavemachinerun since v2 shipped has been missing the embed cardRegression Window
/wavemachinerun on cc-workflow)Steps to Reproduce
/wavemachineagainst any approved multi-wave plan.#wave-statusin Discord during execution.Expected Behavior
BOTH outputs appear:
🏄 Wave N started,✅ Wave N complete,🌊 Wavemachine complete, etc. These are the "what just happened" feed.1/2 — Foundation), Wave (2/2 in phase 1), Action (waiting-on-meatbag — Wave 1 complete), Flight (current/total), Progress bar (20-char Unicode with percentage), Deferrals (pending/accepted).They answer different questions — timeline vs. dashboard — and should coexist.
Reference screenshots attached by BJ on the original chat that surfaced this bug.
Actual Behavior
Only plain-text announcements fire. Embed card never appears. Message ID cache at
.claude/status/discord-status.jsonremains empty (no message to PATCH).Root Cause
From Explore-agent investigation (2026-04-26):
scripts/discord-status-postis fully functional, untouched since commit8da9c9f(2026-02-04). Has debounce (15s, last-write-wins), dev-team fallback, ID caching, PATCH-in-place update logic.37926b2) implicitly relied on this script being invoked on state changes.37926b2) restructured the loop into the top-level Orchestrator and added plain-text event announcements viamcp__disc-server__disc_send, but never added a callsite fordiscord-status-post.~/.claude/skills/wavemachine/SKILL.md) line 99 acknowledges the script exists but treats it as out-of-scope: "If you want live-updating telemetry, look at scripts/discord-status-post (the embed it posts to #wave-status is PATCHed in place on every call) or the Discord notifications..."/nextwaveskill body — no callsite.Classic left-hand / right-hand: the script is there, the skill body references it as a capability, but no code path actually calls it.
Severity
severity::minor— functional regression, not a blocker. Plain-text messages still convey the important state transitions; the embed card was dashboard polish. Restoring it is straightforward (one callsite).Artifacts
scripts/discord-status-post(present, functional)~/.claude/skills/wavemachine/SKILL.mdline 9937926b2("feat(wavemachine): rewrite as top-level loop over /nextwave auto feat(wavemachine): rewrite as top-level loop over /nextwave auto #394")Proposed Fix
Option A — simplest, recommended:
Add a callsite to the
/wavemachineand/nextwaveskill bodies that invokes the existing script at state-change points.In
/wavemachineloop body (after eachwave_complete()returns and at lifecycle transitions — e.g. launch, abort, gate_evaluating):./scripts/discord-status-post \ --channel-id 1487386934094462986 \ --state-dir .claude/status \ --dev-team "cc-workflow"Debounce is already in the script (15s window); rapid state changes don't cause spam.
In
/nextwave(after eachwave_flight_done()for per-flight granularity — matches the original v1 "live-updating" contract):Same invocation.
Why not auto-wire from sdlc-server instead: this is a terminal-side UX tool; keeping it out of MCP handlers (which are platform-neutral) is cleaner. Skill-body invocation is the right layer.
Acceptance Criteria
/wavemachineon any multi-wave plan produces an embed card in#wave-statuswithin 30 seconds of the first state change🏄 Wave N started,✅ Wave N complete, etc.) continue to fire — both outputs coexist.claude/status/state.json,phases-waves.json, andflights.json.claude/status/discord-status.jsonmessage-ID cache is populated after first post; subsequent posts PATCH the cached ID/nextwaveper-flight updates land on the same pinned message (no duplicate pinned messages per wave)Not In Scope
scripts/discord-status-post. It works; just call it.Dependencies
None beyond the existing
scripts/discord-status-post(present) and the skill body files under~/.claude/skills/.Implementation Steps
All changes are skill-body edits — no new code, no new scripts, no MCP changes.
1. Add the embed callsite to
/wavemachine— Launch SequenceIn
~/.claude/skills/wavemachine/SKILL.md, Launch Sequence step 5 (currently line ~84) posts the plain-textdisc_sendfor wavemachine start. Immediately after thatdisc_sendcall, add:This posts the first embed card as soon as state.json is initialized, giving
#wave-statuswatchers a dashboard within seconds of autopilot launch. The script exits cleanly ifstate.jsonis absent (so if the call races initialization, it is a silent no-op).2. Add the embed callsite to
/wavemachine— Loop body, post-wave_completeIn
~/.claude/skills/wavemachine/SKILL.md, The Loop step 4, "OK" branch (currently line ~153): after the existinggenerate-status-panelfire-and-forget call, add:This is the highest-value callsite — it fires once per completed wave, after state.json already reflects the wave's
doneaction and merged-MR metadata. The 15s debounce in the script means that multiple rapid wave completions (unlikely but possible on very fast waves) collapse into a single embed update rather than a flood.3. Add the embed callsite to
/wavemachine— Every terminal exitIn
~/.claude/skills/wavemachine/SKILL.md, Announcements section — the terminal events each already callgenerate-status-panelsynchronously before thedisc_send. Add the embed call immediately aftergenerate-status-paneland beforedisc_sendat each of the five terminal events:wave_next_pending()returned null) — line ~306 areaIn every case the invocation is the same fire-and-forget call. The embed captures the terminal state (the status panel was just regenerated synchronously, so
state.jsonis current); the debounce timer fires 15 seconds later and posts the final embed — the operator sees the closing-frame dashboard in Discord alongside the terminal plain-text announcement.4. Add the embed callsite to
/nextwave— afterwave_flight_doneIn
~/.claude/skills/nextwave/SKILL.md, Prime(post-flight) prompt template, step 5 (line ~327): the step currently reads "Callwave_flight_done(M)after all merges land." Add immediately after that:This is the per-flight granularity callsite — it fires once per flight merge batch, after
wave_flight_donehas updatedflights.jsonwith the completed flight's status. The embed card's Flight field updates live as flights land, which was the original v1 "live-updating" contract.5. State file note — message-ID cache
The script maintains
.claude/status/discord-status.jsonas a message-ID cache for PATCH-in-place behavior. This file does not need to be pre-created — the script creates it on first POST. It should be added to.gitignore(or the existing**/.claude/status/gitignore entry already covers it — verify). The cache survives wavemachine restarts, so a crashed-and-resumed session will PATCH the same pinned message rather than posting a new one.6. Update the reference text in
/wavemachineSKILL.mdIn the Status Panel Lifecycle section (currently line ~99-100), the text reads: "If you want live-updating telemetry, look at
scripts/discord-status-post(the embed it posts to#wave-statusis PATCHed in place on every call) or the Discord notifications..."Update this sentence to reflect that the script is now invoked (not just referenced): "The embed card is posted and PATCHed in place via
scripts/discord-status-post, which fires fire-and-forget at each state-change lifecycle event (wavemachine start, per-wavewave_complete, per-flightwave_flight_done, and every terminal exit)."Test Procedures
This fix is entirely in skill-body prose (SKILL.md files) — there is no automated test suite for skill bodies. Verification is manual, using a real or minimal test campaign.
Manual verification procedure
Precondition: Confirm
scripts/discord-status-postis on PATH or reachable as./scripts/discord-status-postfrom the repo root. Runpython3 scripts/discord-status-post --help— expect the usage block to print cleanly.Confirm bot token: Verify
~/secrets/discord-bot-tokenexists (orDISCORD_BOT_TOKENis set). The script prints a clear error and exits 1 if missing — confirm this before running a campaign.Run a minimal test campaign: Use any approved 1-wave plan (even a single-issue wave on a throwaway branch). Invoke
/wavemachine.Observe in
#wave-status:wave_flight_done): the embed card PATCHes in place — same message, updated Flight field. No new message is posted. This verifies callsite feat: add sync.sh for local-to-repo reverse sync #4 (/nextwavePrime(post-flight) step 5).wave_complete: the embed PATCHes again with the wave'sdoneaction. This verifies callsite feat: complete repo scaffolding — CI, settings, sync, uninstall, changelog #2 (loop step 4 "OK" branch).idleorwaiting-on-meatbag. This verifies callsite feat: add sync.sh for local-to-repo reverse sync #3 (terminal exit path).Confirm PATCH-in-place (no duplicates): Count messages from the bot in
#wave-statusacross the full campaign — there should be exactly ONE embed message, updated in place, regardless of how many waves or flights ran.Confirm plain-text coexistence: Plain-text announcements (
disc_sendcalls) must still fire alongside the embed updates — verify both appear in channel.Confirm
.claude/status/discord-status.jsonis populated: After the first embed post,cat .claude/status/discord-status.jsonshould showchannel_id,message_id, andupdated_atfields.Grep verification (post-edit): After editing the SKILL.md files, confirm callsites are present:
Expect non-zero counts on both. (Pre-edit, wavemachine returns 1 — the reference-only line — and nextwave returns 0.)
Unit tests
N/A — skill bodies have no automated test suite. The script itself (
scripts/discord-status-post) has no test harness in this repo; its correctness is established by the functional verification above and by the fact that it is unchanged since commit8da9c9f.— rules-lawyer 📜 (cc-workflow)