Skip to content

fix(manual-qa): async-dispatch metrics fixes + full tokenomics parity in bundle - #49

Merged
arozumenko merged 2 commits into
mainfrom
feat/manual-qa-async-dispatch-fixes
Jul 31, 2026
Merged

fix(manual-qa): async-dispatch metrics fixes + full tokenomics parity in bundle#49
arozumenko merged 2 commits into
mainfrom
feat/manual-qa-async-dispatch-fixes

Conversation

@olexis-st

Copy link
Copy Markdown
Collaborator

Summary

Claude Code's current build dispatches the Agent tool asynchronously (an immediate "launched" acknowledgment, with real per-dispatch usage delivered later via <task-notification> messages on the orchestrator's own transcript) instead of the synchronous, blocking dispatch the manual-qa metrics pipeline was designed around. Left as-is, this silently breaks per-TC metrics for any project on this bundle: tcs[]/tokens_by_agent go empty, the SubagentStop hook fires prematurely on every orchestrator pause (wiping in-flight trace data), and the ccusage pre-snapshot race can silently fall back to summing every session on the machine instead of just the current run.

All of this was root-caused and fixed against real runs this week in qa-project, verified, then synced into elitea-testing/qa-challenges (2026-07-31). This PR ports the same fixes into the bundle so any project installing manual-qa via npx github:arozumenko/sdlc-skills init --bundle manual-qa gets them too — plus closes a separate, longstanding gap where the bundle's build-run-metrics.mjs had fallen behind its own documentation (knowledge/metrics-format.md already described turns/tokens_by_model/etc. as part of the schema; the code didn't produce them).

Commit 1 — fix(manual-qa): port async-dispatch trace recovery + hook-timing fixes into bundle

  • scripts/resolve-subagent-traces.mjs (new): backfills the tc-trace from the orchestrator transcript's own <task-notification>/<usage> blocks instead of trusting benchmark-tc-hook.mjs's real-time capture (which async dispatch makes unreliable). Wired into benchmark-stop, which now also resolves the true parent transcript for SubagentStop firings (this bundle copy didn't have that resolution at all yet, unlike elitea-testing/qa-challenges).
  • benchmark-stop: re-derives real subagent identity from the payload's own agent_type field (empty on a premature firing, "test-reporter" only on genuine completion) instead of trusting the SubagentStop matcher alone; elapsed-time-since-last-dispatch kept as a secondary net. Debug logging added (gitignored).
  • scripts/ccusage-wait.sh (new) + use in benchmark-session-start/benchmark-preflight: retries ccusage session --json for up to 60s of real elapsed time (not a fixed attempt count — disproven by real timing data) until the session's own entry appears, instead of one un-retried call.

Commit 2 — feat(manual-qa): bring bundle's build-run-metrics.mjs to full tokenomics parity

  • build-run-metrics.mjs: ported the fuller feature set already present in elitea-testing/qa-challenges/qa-projectcountTurns() (via the transcript_path resolved in commit 1), subagent_dispatches, orchestrator_cost_pct, tokens_by_model + cache_read_share_pct (keyed off ccusage's own scoped modelsUsed[]), models_used.
  • benchmark-stop: now actually passes transcript_path through to build-run-metrics.mjs (resolved in commit 1 for resolve-subagent-traces.mjs's sake only, until this commit could consume it too).
  • Diffed build-tokenomics-report.mjs/build-tokenomics-html.mjs/compare-runs.mjs against elitea-testing for completeness — already at parity (compare-runs.mjs doesn't even exist there — bundle is ahead). Docs (knowledge/metrics-format.md, hooks/README.md, templates) were already accurate for this schema — no doc changes needed, only the code catching up to what they promised.

Test plan

  • Replayed real SubagentStop payloads (captured live in qa-project) through the full benchmark-stop -> resolve-subagent-traces.mjs -> build-run-metrics.mjs chain in an isolated scratch dir — premature firings correctly skipped, genuine completion correctly proceeds.
  • resolve-subagent-traces.mjs run against a real orchestrator transcript — output matched that run's actual Performance Metrics table exactly.
  • ccusage-wait.sh run against a real, already-indexed session id — immediate scoped match; against a synthetic non-existent id — correctly exhausts the 60s budget and falls back without hanging.
  • Full chain verified end-to-end: real turns/subagent_dispatches/tokens_by_model/cache_read_share_pct/models_used/tokens_by_agent all populate correctly, real per-TC tokens/tool_uses match the source transcript.
  • All scratch-directory testing — no production repo state (this bundle's own reports/ etc.) was touched during verification.

🤖 Generated with Claude Code

…s into bundle

Claude Code's current build dispatches the Agent tool asynchronously (an
immediate "launched" acknowledgment, with real per-dispatch usage delivered
later via <task-notification> messages on the orchestrator's own transcript)
instead of the synchronous, blocking dispatch this pipeline was designed
around. Left as-is, that silently breaks per-TC metrics for any project on
this bundle re-run today: benchmark-tc-hook.mjs's real-time PostToolUse
capture never sees usage data, so tcs[] and tokens_by_agent go empty and the
whole session's tokens get misattributed to test-run-lead. The same
async-dispatch behavior also makes the SubagentStop hook fire on every
orchestrator pause while a background task is still running, not just on
the real test-reporter completion, repeatedly wiping in-flight trace data
before a suite finishes. A related but separate issue: the ccusage
pre-snapshot taken at SessionStart is usually captured before Claude Code's
own usage logs have indexed the brand-new session at all, so session-level
token/cost figures silently fall back to summing every session on the
machine instead of just this run's.

All three were root-caused and fixed against real runs in qa-project this
week, then synced into elitea-testing/qa-challenges (2026-07-31) — this
change ports the same three fixes into the bundle so any project installing
manual-qa from here on gets them too:

- scripts/resolve-subagent-traces.mjs (new): backfills the tc-trace from
  the orchestrator transcript's own <task-notification>/<usage> blocks
  instead of trusting benchmark-tc-hook.mjs's real-time capture. Wired into
  benchmark-stop, which now also resolves the true parent transcript for
  SubagentStop firings (this bundle copy didn't have that resolution at
  all yet, unlike elitea-testing/qa-challenges — ported as a prerequisite).
- benchmark-stop: re-derives real subagent identity from the payload's own
  agent_type field (empty string on a premature firing, "test-reporter"
  only on the genuine completion) instead of trusting the SubagentStop
  matcher, which doesn't reliably filter on affected Claude Code builds;
  elapsed-time-since-last-dispatch kept as a secondary net for payload
  shapes without that field. Debug logging added (gitignored via the
  existing *.log rule) so any future payload-shape drift is directly
  observable instead of re-diagnosed from scratch.
- scripts/ccusage-wait.sh (new) + its use in benchmark-session-start and
  benchmark-preflight: retries ccusage session --json for up to 60s of real
  elapsed time (not a fixed attempt count — an earlier count-based attempt
  was disproven by real timing data) until this session's own entry
  appears, instead of a single immediate un-retried call. benchmark-preflight
  now also verifies session-start's snapshot is actually scoped before
  reusing it, rather than propagating an unscoped give-up forward.

Deliberately NOT touched: build-run-metrics.mjs's turns/tokens_by_model/
subagent_dispatches instrumentation is still absent from this bundle copy
(a separate, previously-deferred porting decision, see project memory) —
transcript_path is resolved in benchmark-stop only as far as
resolve-subagent-traces.mjs needs it, not plumbed into build-run-metrics.mjs
itself. hooks/README.md's file count updated (10 -> 12) for the two new
scripts; no other docs/behavior changed.

Verified end-to-end in isolated scratch directories against real captured
data: replayed real SubagentStop payloads (captured live in qa-project) to
confirm the premature-firing guard skips correctly and proceeds on the
genuine completion; ran resolve-subagent-traces.mjs against a real
orchestrator transcript and confirmed its output matches that run's actual
Performance Metrics table exactly; ran ccusage-wait.sh against a real,
already-indexed session id and confirmed immediate scoped match; ran the
full benchmark-stop -> resolve-subagent-traces.mjs -> build-run-metrics.mjs
chain and confirmed real per-TC tokens/tool_uses in the output with
turns/tokens_by_model correctly still absent (out of scope for this change).
…ics parity

The bundle carried a leaner, older build-run-metrics.mjs (615 lines, no
turns/subagent_dispatches/orchestrator_cost_pct/tokens_by_model/
cache_read_share_pct/scopedModelsUsed) while its own
knowledge/metrics-format.md already documented and gave a worked example of
the FULLER schema, and benchmark-stop's own comment explicitly flagged the
gap ("this bundle copy doesn't plumb transcript_path through to
build-run-metrics.mjs at all yet ... not applicable here until that feature
gap is ported"). Docs and code had drifted apart; this closes that gap so
a fresh `npx github:arozumenko/sdlc-skills init --bundle manual-qa` install
actually produces what its own documentation already promises.

Diffed the bundle's build-run-metrics.mjs against elitea-testing's current
copy (freshest reference, post yesterday's async-dispatch sync) plus
build-tokenomics-report.mjs/build-tokenomics-html.mjs/compare-runs.mjs for
completeness: those three were already fully at parity (compare-runs.mjs
doesn't even exist in elitea-testing/qa-challenges — the bundle is ahead
there), narrowing the real gap to exactly one file.

- build-run-metrics.mjs: ported the full feature set from qa-project's
  current copy (already verified feature-identical to elitea-testing's,
  and already using this bundle's own env-var-based PROJECT_DIR convention,
  so the port needed no structural adaptation) — countTurns() from a
  transcript_path argument, subagent_dispatches, orchestrator_cost_pct
  (token-share proxy, documented as such), tokens_by_model +
  cache_read_share_pct (keyed off ccusage's own scoped modelsUsed[], not
  hardcoded model ids), and models_used.
- benchmark-stop: now actually passes transcript_path through to
  build-run-metrics.mjs (resolved in the previous commit for
  resolve-subagent-traces.mjs's sake, but deliberately withheld from this
  call since the old build-run-metrics.mjs couldn't consume it yet).

Not touched: knowledge/metrics-format.md, hooks/README.md, and
hooks/templates/*.template.md were all already accurate/complete for this
schema (checked field-by-field) — no doc changes needed, only the code
producing what they already promised.

Verified end-to-end in an isolated scratch directory: replayed real
SubagentStop payloads (captured live in qa-project) through the full
benchmark-stop -> resolve-subagent-traces.mjs -> build-run-metrics.mjs
chain and confirmed real turns/subagent_dispatches/tokens_by_model/
cache_read_share_pct/models_used/tokens_by_agent all populate correctly
alongside the exact real per-TC tokens/tool_uses from a real captured
transcript.
@olexis-st
olexis-st requested a review from arozumenko July 31, 2026 11:12
@arozumenko
arozumenko merged commit 8147701 into main Jul 31, 2026
2 checks passed
@arozumenko
arozumenko deleted the feat/manual-qa-async-dispatch-fixes branch July 31, 2026 12:33
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.

2 participants