Skip to content

fix: surface retry/backoff status in the CLI#3325

Merged
praisonai-triage-agent[bot] merged 2 commits into
mainfrom
claude/issue-3311-20260723-1026
Jul 23, 2026
Merged

fix: surface retry/backoff status in the CLI#3325
praisonai-triage-agent[bot] merged 2 commits into
mainfrom
claude/issue-3311-20260723-1026

Conversation

@praisonai-triage-agent

Copy link
Copy Markdown
Contributor

Fixes #3311

Summary

When a model call hits a rate limit or transient error, the runtime backs off and retries (honouring retry-after), but the wait was silent — a multi-second backoff was indistinguishable from a frozen CLI. This surfaces the retry signal as a first-class streaming event and maps it into the CLI NDJSON stream so a live 'retrying in Ns (attempt k/N)' status is available.

Changes (minimal, lightweight)

  • Core streaming/events.py: add StreamEventType.RETRY.
  • Core agent/chat_mixin.py: after the existing ON_RETRY hook fires, emit a RETRY StreamEvent (carrying attempt, max_attempts, delay, reason in metadata) in both the sync and async retry loops, via a small _emit_retry_stream_event helper guarded by emitter.has_callbacks — zero-overhead when nothing is listening. The retry-after backoff logic is unchanged.
  • Wrapper CLI praisonai_code/cli/output/event_bridge.py: map retry to a canonical run.retry NDJSON event so --output stream-json consumers get a live retry signal. Non-JSON/silent modes stay silent; the event stays available programmatically to Python/YAML runs.

Architecture / scope review

No new Agent params, modules, or exports. Reuses the existing stream_emitter, StreamEvent.metadata, and the existing bridge dispatch. Keeps the package lightweight.

Tests

  • test_streaming_events.py — StreamEventType.RETRY exists; helper emits correct metadata and is a no-op without callbacks (27 passed).
  • test_run_event_bridge.py — retry maps to run.retry in stream-json mode and stays silent in text mode (11 passed).

Generated with Claude Code

@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@MervinPraison, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 76dae12a-cb7a-433a-83ed-14f456b763a3

📥 Commits

Reviewing files that changed from the base of the PR and between 7494bba and 6a97836.

📒 Files selected for processing (5)
  • src/praisonai-agents/praisonaiagents/agent/chat_mixin.py
  • src/praisonai-agents/praisonaiagents/streaming/events.py
  • src/praisonai-agents/tests/unit/test_streaming_events.py
  • src/praisonai-code/praisonai_code/cli/output/event_bridge.py
  • src/praisonai/tests/unit/cli/test_run_event_bridge.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-3311-20260723-1026

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This follow-up refactors retry stream-event construction and emission while expanding coverage for asynchronous callbacks and non-JSON output.

  • Adds shared synchronous and asynchronous retry-event helpers.
  • Routes async retries through the asynchronous emitter.
  • Adds retry-event helper and CLI bridge tests.

Confidence Score: 4/5

The PR is not yet safe to merge because retry status events remain one attempt behind the model call they announce.

Both retry loops still emit attempt + 1 after that indexed call has failed, so the first backoff reports attempt 1 while preparing model-call attempt 2.

src/praisonai-agents/praisonaiagents/agent/chat_mixin.py

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/agent/chat_mixin.py Refactors retry event emission and fixes async callback delivery, but retains the incorrect retry attempt number in both paths.
src/praisonai-agents/tests/unit/test_streaming_events.py Adds focused coverage for helper emission, no-listener behavior, and async-only callbacks.
src/praisonai/tests/unit/cli/test_run_event_bridge.py Extends retry bridge coverage for schema versioning and silent text mode.

Reviews (3): Last reviewed commit: "fix: dispatch async retry stream events ..." | Re-trigger Greptile

Comment thread src/praisonai-agents/praisonaiagents/agent/chat_mixin.py
Comment thread src/praisonai-agents/praisonaiagents/agent/chat_mixin.py Outdated
@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jul 23, 2026
@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
  6. Repo routing: agent-callable tools → PraisonAI-Tools; lifecycle plugins → PraisonAI-Plugins; optional sandbox backends → PraisonAI-Plugins (praisonai.sandbox entry point) — request changes if wrongly added to praisonaiagents/

Phase 2: FIX Valid Issues
7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
8. Also independently identify and fix any gaps or issues you find in the changed code — do not rely only on prior reviewer feedback
9. Push all code fixes directly to THIS branch (do NOT create a new PR)
10. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
11. If all issues are resolved, approve the PR / close the Issue
12. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jul 23, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #3325 — ✅ Approved

  • Read AGENTS.md + changed files
  • Validate reviewer issues (P1 attempt offset, P2 async callbacks)
  • Independent architecture review
  • Fix valid issues
  • Run tests (streaming: 29 passed, bridge: 11 passed)
  • Push + final verdict

Phase 1 — Architecture (per AGENTS.md)

Check Result
Protocol-driven ✅ Reuses StreamEventType/StreamEvent/StreamEventEmitter; only a new enum member in core.
Backward compatible ✅ Additive enum value + guarded helpers. No signature/behavior changes to the retry-after backoff logic.
Performance (hot path) has_callbacks guard → zero-overhead when nothing listens. Emission only fires on the rare retry path, never per-token.
SDK value ✅ Genuine: a silent multi-second backoff was indistinguishable from a hang. This surfaces a first-class run.retry signal for CLI/programmatic consumers. Not scope creep.
Agent bloat ✅ No new Agent params/exports; helpers are private methods on the chat mixin.
Repo routing ✅ Correct — core streaming protocol in praisonaiagents/, CLI mapping in praisonai-code/. Not a tool/plugin.

Phase 2 — Reviewer feedback triage

  • Greptile P1 — "attempt number offset" → NOT a bug (skipped). The emitted attempt = attempt+1 over max_attempts is intentionally consistent with the pre-existing OnRetry hook (retry_count=attempt+1, max_retries) and the existing log line Retry {attempt+1}/{max_attempts}. "Retry 1/N" is a valid convention; changing it would make the stream event disagree with the hook and logs. No change.
  • Greptile P2 — "async retry callbacks skipped" → VALID, FIXED. In the async retry loop, an async-only consumer (add_async_callback()) passed has_callbacks but the sync emit() never visited async callbacks, so the RETRY event was silently dropped. Fixed with a new _aemit_retry_stream_event that uses emit_async (falls back to emit), with construction shared via _build_retry_stream_event. The async loop now awaits it.

Files modified (this review)

  • src/praisonai-agents/praisonaiagents/agent/chat_mixin.py — added _aemit_retry_stream_event (async path) + shared _build_retry_stream_event; async retry loop now uses the async emitter. agent_id preserved.
  • src/praisonai-agents/tests/unit/test_streaming_events.py — added 2 tests: async-only callback receives RETRY; async no-op without callbacks.

Tests

  • test_streaming_events.py29 passed (incl. 2 new async tests).
  • test_run_event_bridge.py11 passed (no regression).

Verdict

Lightweight, protocol-driven, backward-compatible, no hot-path regression. Valid P2 bug fixed; P1 correctly assessed as a non-issue. Approving.

Job run · branch claude/issue-3311-20260723-1026

Comment thread src/praisonai-agents/praisonaiagents/agent/chat_mixin.py
@praisonai-triage-agent praisonai-triage-agent Bot added pipeline/merge-ready Eligible for merge gate auto-merge and removed pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown labels Jul 23, 2026
@MervinPraison

Copy link
Copy Markdown
Owner

@claude this PR has merge conflicts with main. Please rebase onto latest main, resolve conflicts (keep this PR's intent, merge in newer main logic), run targeted tests, and force-push with --force-with-lease. Comment which files you resolved. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params. Keep the resolution minimal — the aim is a lightweight and powerful package; do not introduce scope creep while resolving conflicts.

@praisonai-triage-agent praisonai-triage-agent Bot added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:conflict Blocked: merge conflict or rebase pending pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/merge-ready Eligible for merge gate auto-merge labels Jul 23, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Rebased PR #3325 onto latest main

  • Setup git identity & fetch main
  • Rebase onto origin/main, resolve conflicts (keep PR intent + newer main logic)
  • Run targeted tests
  • Force-push with --force-with-lease
  • Summarize resolved files

Conflicts resolved (5 files)

All 5 changed files conflicted because main (via merged PR #3317) already landed a parallel retry-event implementation. I merged both, keeping this PR's intent and the newer helper-based structure:

File Resolution
praisonaiagents/streaming/events.py Kept single StreamEventType.RETRY = "retry" (both sides added the same member).
praisonaiagents/agent/chat_mixin.py Adopted the helper-based approach from the P2 fix commit: both sync and async retry loops now call _emit_retry_stream_event / _aemit_retry_stream_event (shared _build_retry_stream_event). The async path uses emit_async so async-only add_async_callback() consumers receive RETRY events — the correct fix, not inline dup. Backoff/retry-after logic unchanged.
praisonai-code/.../cli/output/event_bridge.py Kept canonical run.retry mapping (attempt/max_attempts/delay/reason), consistent with main's schema_version-stamped emit.
tests/unit/test_streaming_events.py Kept the fuller suite: metadata test + sync helper no-op/emit tests + 2 async tests (async-only callback delivery, async no-op).
praisonai/tests/unit/cli/test_run_event_bridge.py Kept run.retry mapping test plus schema_version assertion and the new test_retry_event_silent_in_text_mode.

Skipped / not changed: Greptile P1 "attempt offset" — intentionally left as-is (attempt k+1/N matches the existing OnRetry hook retry_count and the Retry k/N log line; "Retry 1/N" is the established convention). No new Agent params, modules, or exports — scope stayed lightweight.

Tests (rebased HEAD)

  • test_streaming_events.py29 passed
  • test_run_event_bridge.py11 passed

Force-pushed 8b14d3b54…1aff8c7d6 → branch is now conflict-free against main.

Job run · branch claude/issue-3311-20260723-1026

Add StreamEventType.RETRY emitted alongside the existing ON_RETRY hook (guarded by has_callbacks for zero-overhead) in both sync and async retry loops, carrying attempt/max_attempts/delay/reason in metadata. Map it to a canonical run.retry NDJSON event in the CLI stream-json bridge so consumers get a live 'retrying in Ns (attempt k/N)' signal. Non-JSON/silent modes stay silent.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@praisonai-triage-agent praisonai-triage-agent Bot added pipeline/merge-ready Eligible for merge gate auto-merge claude-merge-gate-active pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI and removed pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:cooldown Blocked: post-push or @claude cooldown claude-merge-gate-active pipeline/merge-ready Eligible for merge gate auto-merge pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI labels Jul 23, 2026
@praisonai-triage-agent

Copy link
Copy Markdown
Contributor Author

Merge gate scan — eligible for assessment. Claude merge gate will assess and may auto-merge if MERGE_GATE_VERDICT: APPROVE.

@praisonai-triage-agent
praisonai-triage-agent Bot merged commit 56326b7 into main Jul 23, 2026
44 checks passed
@praisonai-triage-agent

Copy link
Copy Markdown
Contributor Author

Merged by Claude PR merge gate (claude-merge-gate.yml).
Verdict: MERGE_GATE_VERDICT: APPROVE
SHA: 1aff8c7
Method: merge

@praisonai-triage-agent

Copy link
Copy Markdown
Contributor Author

Merge gate scan — not eligible for auto-merge.

  • not open
  • already merged by gate
  • mergeState=UNKNOWN

Actions: wait for CI and the Claude review chain, or add label needs-manual-review and merge manually.
Opt out: label no-auto-merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merged-by-gate pipeline/merge-ready Eligible for merge gate auto-merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface retry/backoff status in the CLI so rate-limit waits aren't mistaken for a hang

1 participant