Skip to content

bug(wavemachine): Discord embed card dropped in v2 rewrite — plain-text only, no auto-updating dashboard #502

@bakeb7j0

Description

@bakeb7j0

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 /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

  1. Run /wavemachine against any approved multi-wave plan.
  2. Watch #wave-status in Discord during execution.
  3. Observe: plain-text announcements fire on start, wave complete, wavemachine complete, abort.
  4. 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:

  1. Plain-text event announcements (current) — the timeline. 🏄 Wave N started, ✅ Wave N complete, 🌊 Wavemachine complete, etc. These are the "what just happened" feed.
  2. 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).

Artifacts

Proposed Fix

Option A — simplest, recommended:

Add a callsite to the /wavemachine and /nextwave skill bodies that invokes the existing script at state-change points.

In /wavemachine loop body (after each wave_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 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:

Fire-and-forget: ./scripts/discord-status-post --channel-id 1487386934094462986 --state-dir .claude/status

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:

Fire-and-forget: ./scripts/discord-status-post --channel-id 1487386934094462986 --state-dir .claude/status

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

  1. 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.

  2. 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.

  3. Run a minimal test campaign: Use any approved 1-wave plan (even a single-issue wave on a throwaway branch). Invoke /wavemachine.

  4. Observe in #wave-status:

  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.

  6. Confirm plain-text coexistence: Plain-text announcements (disc_send calls) must still fire alongside the embed updates — verify both appear in channel.

  7. 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.

  8. Grep verification (post-edit): After editing the SKILL.md files, confirm callsites are present:

    grep -c "discord-status-post" ~/.claude/skills/wavemachine/SKILL.md
    grep -c "discord-status-post" ~/.claude/skills/nextwave/SKILL.md

    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.

rules-lawyer 📜 (cc-workflow)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions