fix(channels): render the approval card on a park, answer by the real interaction id - #6653
fix(channels): render the approval card on a park, answer by the real interaction id#6653mmabrouk wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: 📝 SummarySummary by CodeRabbit
WalkthroughThe records worker now publishes terminal turn events for committed terminal records. The channels outbox resolves approval interaction tokens to database row IDs before storing pending choices. Production wiring and unit tests provide the interaction service. ChangesApproval turn completion
Priority: ➖ Normal — Schedule this approval-flow fix because parked turns and detached continuations need reliable approval cards and correct interaction IDs. Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: 🟡 Moderate · up to Parked approval cards can fail to render when two terminal turns for one session are committed together, leaving an approval flow without its completion notification. The event collection must preserve every session-and-turn pair before this change is merge-ready. Sequence Diagram(s)sequenceDiagram
participant RecordsWorker
participant ChannelsOutboxWorker
participant SessionInteractionsService
participant ChannelsService
RecordsWorker->>ChannelsOutboxWorker: publish terminal turn event
ChannelsOutboxWorker->>SessionInteractionsService: resolve approval token
SessionInteractionsService-->>ChannelsOutboxWorker: return interaction row ID
ChannelsOutboxWorker->>ChannelsService: store pending choice
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
9620117 to
9e94419
Compare
|
@coderabbitai review |
|
f66271d to
89f4dd9
Compare
9e94419 to
16c494e
Compare
|
Codex (astra, medium, simplify) reviewed this lane: "request changes" on an ordering race plus two smaller points, all applied in the last commit:
Codex also confirmed @coderabbitai review |
🧩 Analysis chain🏁 Script executed: Length of output: 2294 🏁 Script executed: Length of output: 94093 🏁 Script executed: Length of output: 1720 🏁 Script executed: Length of output: 145178 🏁 Script executed: Length of output: 59441
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
api/oss/tests/pytest/unit/channels/test_channels_outbox_worker.py (1)
427-427: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest exact-token selection with multiple interactions.
The fake returns one row with the matching token. Both the exact-match path and the single-row fallback return the same ID. A regression that always selects the first interaction would pass.
Add a case with two rows where the matching token is on the second row. Assert that the stored ID is the second row ID.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Advanced
Run ID: dfa0eeb0-0ab0-46ab-b9a2-a0efd79dadb1
📒 Files selected for processing (4)
api/entrypoints/worker_streams.pyapi/oss/src/tasks/asyncio/channels/outbox.pyapi/oss/src/tasks/asyncio/sessions/records_worker.pyapi/oss/tests/pytest/unit/channels/test_channels_outbox_worker.py
Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.
| terminal: Dict[str, str] = {} | ||
| for record in events: | ||
| if record.record_type == TERMINAL_RECORD_TYPE and record.turn_id: | ||
| terminal[record.session_id] = str(record.turn_id) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve each terminal turn in the batch.
This mapping uses only session_id as its key. If one committed batch contains terminal records for two turns in the same session, the later record overwrites the earlier turn ID. The publish loop then omits turn_ended for the overwritten turn, so its outbox result can remain unrendered.
Store distinct (session_id, turn_id) pairs and add a regression test with two terminal turns for one session.
…real interaction id This completes the approval loop (F100/F101), which PR #6650 left with a working answer path but no visible card. - A parked turn never calls complete_turn, so the channels outbox was never told to fold it and the card never drew. The record-ingress now publishes a turn-ended to streams:sessions on every terminal record. That stream is consumed only by the channels outbox, and the outbox keys its output by (turn_id, index), so a turn that also ends through complete_turn just edits the same message instead of posting twice. It also covers the approval continuation, a detached run that bypasses complete_turn the same way. - The outbox stored the fold's ACP token as the pending choice's interaction id; the sessions respond path answers by the SessionInteraction row id. The outbox now resolves the row id for the turn's open interaction (matched on the token) before storing it. Verified live on the Agenta channel: the card renders, an "approve" reply resumes the parked turn, the approved tool runs, and the agent's answer ("Done! I've created hello.txt...") posts back. A normal turn still answers exactly once, no duplicate. Claude-Session: https://claude.ai/code/session_01HXt8WBUK9MrWKNTYbUKpaT
- Ordering race: the turn-ended for a parked turn or a continuation was published from the record INGRESS, before the record committed, so the channels outbox could fold stale content and, because it stops on any existing answer, acknowledge it permanently. Move the signal to the post-commit records-worker path (terminal_turns_in_batch over the committed records), so the record is durable before the outbox reads it. - The interaction lookup took rows[0] on a token mismatch, which could answer the wrong interaction when a turn holds resolved approvals beside the open one. Require an exact token match, and fall back only when the turn has exactly one interaction. - Drop the broad except that swallowed a lookup failure into a None id (an unusable card); let the stream worker's retry handle a transient failure instead. Re-verified live on the Agenta channel: card renders, approve resumes, the file is created, the agent posts "Done...". A normal turn answers once. Claude-Session: https://claude.ai/code/session_01HXt8WBUK9MrWKNTYbUKpaT
89f4dd9 to
7af9cd7
Compare
7e1d388 to
b37c8b2
Compare
Context
Completes approvals (F100, F101). PR #6650 built the answer path but live QA showed the card never rendered, so the loop could not be driven. Two gaps, both fixed here.
Changes
complete_turn, so the channels outbox was never told to fold it. The record-ingress publishes aturn_endedtostreams:sessionson every terminal record. That stream is consumed only by the channels outbox, which keys its output by(turn_id, index), so a turn that also ends throughcomplete_turnedits the same message rather than posting twice. This also covers the approval continuation, a detached run that bypassescomplete_turnthe same way.SessionInteractionrow id. The outbox now resolves the row id for the turn's open interaction, matched on the token, before storing it.Tests
ask-mode Claude agent: the card renders in 7.7 s, an "approve" reply resumes the parked turn, the approved Write runs, and the agent posts "Done! I've created hello.txt...". A normal turn still answers exactly once, with no duplicate outbox row.Note
This publishes a turn-ended on every terminal record. I considered a dedicated
turn_pausedevent instead (cleaner vocabulary), but the outbox's existing(turn_id, index)dedup makes the single-event approach safe and far smaller. The decision and the alternative are in the takeover record.https://claude.ai/code/session_01HXt8WBUK9MrWKNTYbUKpaT