Troubleshooting
Fix missing, repeated, late, delayed, or noisy announcements by checking each step from the framework event to the browser update.
Nothing is announced
First check that the runtime receives a started event, new text only, and one completed, failed, or interrupted event with the same response ID. Then check runtime diagnostics and the browser result separately.
- Confirm the announcement listener or DOM binding is attached before dispatch.
- Complete a sentence or dispatch
response.completedso buffered text can flush. - Check for policy-silent, empty-text, unknown-response, or runtime-disposed diagnostics.
- Check DOMDeliveryResult for unavailable or disposed delivery.
- A successful page update does not confirm what a screen reader spoke. Test that separately.
Output repeats or arrives too often
response.text.delta must contain only the text that just arrived. If you send the full response every time, earlier text may be announced again.
- Dispatch append-only deltas rather than accumulated message text.
- Keep
responseIdandresponseInstanceIdstable for one active attempt. - Increase
text.minimumCharactersor minimumGapMs when valid updates are too granular. - Inspect duplicate and coalesced diagnostics before changing policy.
Old output appears after retry
A retry must replace the active response attempt. Core ignores late updates from older attempts and reports stale-response diagnostics.
- Send
response.retryingwith the replaced and next response instance IDs. - Attach the new
responseInstanceIdto each later text event and final event. - Do not reuse a completed or interrupted response instance.
- If the framework does not provide retry IDs, report retry events directly from your app instead of guessing.
Tool progress is too noisy
Keep each tool ID stable and use short, translated labels. To reduce noise, adjust the start delay and progress settings instead of filtering announcements after they are created.
- Delay tool starts with
tools.announceStartAfterMsso fast operations can finish quietly. - Raise
tools.progressEveryPercentor disable progress announcements. - Never copy raw arguments, results, or backend errors into user-facing labels.
Use diagnostics to find the first problem
Each diagnostic says what the runtime did and why. Start with the first unexpected result instead of looking only at the final browser update.
| Reason | Meaning | First check |
|---|---|---|
| policy-silent | Active policy suppresses this event | Preset and explicit policy overrides |
| unknown-response or unknown-tool | No active matching identity exists | Started event order and IDs |
| stale-response or stale-tool | Event belongs to a replaced execution | Instance identity propagation |
| progress-threshold | Progress did not cross the configured bucket | progressEveryPercent |
| queue-capacity | Bounded scheduler rejected additional work | Producer rate and maxQueueSize |
| delivery-error | Every announcement listener failed | Listener exceptions and onDeliveryError |
| delivered | Listeners accepted the prepared announcement | Check the DOM result, then test with a screen reader |
Inspect a redacted trace during development
Use @generative-a11y/devtools when a single diagnostic is not enough to explain the event sequence. Its bounded store connects source events, runtime decisions, snapshots, and optional DOM delivery results without retaining response text, labels, tool data, or page content.
- Attach the store to an existing runtime and give that runtime a stable development ID.
- Forward DOMDeliveryResult values to recordDelivery when you need browser correlation.
- Pause or clear capture without changing runtime scheduling or browser delivery.
- Treat the trace as debugging evidence, not proof of screen-reader speech.