Skip to content

fix(app): handle question response races clearly#839

Merged
Astro-Han merged 5 commits into
devfrom
pawwork/fix-question-dock-object-error
May 21, 2026
Merged

fix(app): handle question response races clearly#839
Astro-Han merged 5 commits into
devfrom
pawwork/fix-question-dock-object-error

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Keep the question dock open on failed /session/:id/tool/respond attempts so users can correct and retry without losing drafts.
  • Normalize question response errors locally so plain object failures never surface as [object Object], including plain body no_pending_tool_call and decoder-detail errors.
  • Treat already_resolved as idempotent completion only when it matches a request that this dock already submitted, and keep the stale local dock reactively non-interactive after pending/confirmed completion until sync removes it.

Why

Issue #837 reported that answering a question dock could show unreadable [object Object] errors and close the dock before users could fix decoder/payload failures. The fix keeps retryable failures interactive while preserving idempotent completion for the same locally submitted request.

Related Issue

Fixes #837

Human Review Status

Pending

Review Focus

  • Confirm already_resolved only completes after the same local question request was submitted.
  • Confirm plain body no_pending_tool_call maps to stale-session copy, and structured decoder errors keep readable details without [object Object].
  • Confirm pending and post-success waiting-for-sync states block duplicate submit/dismiss/selection interactions and now drive reactive disabled state.

Risk Notes

Skipped checklist items:

  • Manual visible UI/copy check: not run locally; this change is covered by focused unit tests, browser-reactivity check, and typecheck, but it does alter error-copy routing.
  • Platform impact: no platform, packaging, updater, signing, path, shell, or permission surface touched.
  • Docs/release/dependencies/etc.: none touched.

How To Verify

Focused question dock tests: 15 passed / 0 failed
Command: bun test --preload ./happydom.ts src/pages/session/composer/session-question-dock.test.ts

App typecheck: passed
Command: bun run typecheck

Diff check: passed with no whitespace errors
Command: git diff --check

Screenshots or Recordings

Not provided. No screenshot/recording was captured for this copy/error-state change.

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Question dock now prevents duplicate submissions while awaiting responses by disabling controls during request processing.
    • Improved error handling for tool responses with better detection of stale sessions and invalid requests, providing clearer user feedback.
  • Tests

    • Added end-to-end and unit tests to verify question dock prevents overlapping submissions and handles edge cases correctly.

Review Change Stack

@Astro-Han Astro-Han added bug Something isn't working P2 Medium priority app Application behavior and product flows ui Design system and user interface labels May 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes user-path files (packages/app/src/pages/session/composer/session-question-dock.test.ts, packages/app/src/pages/session/composer/session-question-dock.tsx)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Astro-Han has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 54 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 22780299-776d-40de-baea-c26939f6c2a1

📥 Commits

Reviewing files that changed from the base of the PR and between b8732de and dfd9c03.

📒 Files selected for processing (4)
  • packages/app/e2e/session/session-composer-dock.spec.ts
  • packages/app/src/i18n/en.ts
  • packages/app/src/i18n/zh.ts
  • packages/app/src/pages/session/composer/session-question-dock.tsx
📝 Walkthrough

Walkthrough

The PR adds request-scoped response guards and error normalization to the question dock to prevent duplicate submissions and eliminate misleading "[object Object]" error messages. A new createQuestionResponseGuard() gates all interactions during pending requests, while normalizeToolRespondError() classifies failures into actionable, human-readable categories. All UI controls now respect the guard's canInteract() state instead of a simple boolean.

Changes

Question Dock Response Guard and Error Handling

Layer / File(s) Summary
Response guard and error normalization helpers
packages/app/src/pages/session/composer/session-question-dock.tsx
New exported functions createQuestionResponseGuard(), normalizeToolRespondError(), and isSameQuestionRequest() provide request-scoped interaction control, error classification without stringification, and duplicate detection via request fingerprints.
Component state and guard initialization
packages/app/src/pages/session/composer/session-question-dock.tsx
Component now instantiates the response guard for the current request and tracks locallySubmitted fingerprint to detect repeated submissions from the same user interaction.
Request lifecycle: complete and fail handlers
packages/app/src/pages/session/composer/session-question-dock.tsx
Added complete() handler that confirms the guard and clears state on success; refactored fail() handler normalizes errors, transitions the guard, determines idempotent "already resolved" cases, and shows readable error toasts. Both replyMutation and rejectMutation route through these handlers.
UI control disabling with !canInteract()
packages/app/src/pages/session/composer/session-question-dock.tsx
All interaction handlers and disabled props across buttons, textarea, options, and custom controls now gate on !canInteract() instead of sending(), preventing duplicate submissions while response is pending.
Unit tests for response guard helpers
packages/app/src/pages/session/composer/session-question-dock.test.ts
Tests validate that normalizeToolRespondError() classifies errors and never outputs "[object Object]", isSameQuestionRequest() matches requests correctly, and createQuestionResponseGuard() persists disabled state reactively through begin/fail/confirm transitions and retry flows.
End-to-end test for blocked dock
packages/app/e2e/session/session-composer-dock.spec.ts
E2E test verifies that all dock controls become disabled while a stalled tool respond request is pending, with proper cleanup after response release.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly Related PRs

  • Astro-Han/pawwork#569: Modifies the same session-composer-dock e2e test file for blocked-question behavior, directly overlapping the dock test scenario.
  • Astro-Han/pawwork#353: Modifies session-question-dock.tsx footer action controls; the new guard-based canInteract() state now gates those skip/submit actions.

Poem

A guard stands sentinel at the dock's gate,
Classifying each failure to fate:
No more [object Object] in sight,
Just readable errors shining bright! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(app): handle question response races clearly' directly summarizes the main change: addressing race conditions in question dock response handling to provide clear, readable error messages.
Description check ✅ Passed The PR description follows the template structure comprehensively with Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify, and a completed Checklist, addressing all major sections.
Linked Issues check ✅ Passed The PR fully addresses issue #837 objectives: normalizes error messages (preventing [object Object] display), treats already_resolved as idempotent only for matching requests, maps plain no_pending_tool_call to stale-session, and keeps dock interactive on retryable failures via createQuestionResponseGuard.
Out of Scope Changes check ✅ Passed All changes are scoped to question dock response handling, error normalization, and request matching logic. No unrelated refactors, dependency changes, or file modifications beyond the stated scope are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pawwork/fix-question-dock-object-error

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 and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves error handling in the SessionQuestionDock component by introducing a normalization layer for API errors and a local submission guard to handle idempotent 'already_resolved' responses. It also includes comprehensive unit tests for these new utilities. Feedback was provided regarding the fallback error message in the fail handler, suggesting that using a generic 'invalid payload' message may be misleading and that specific error reasons from the API should be surfaced instead.

Comment thread packages/app/src/pages/session/composer/session-question-dock.tsx
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown

Perf delta summary

Comparator: pass

Profile / Scenario interaction median interaction worst long task max tbt frame gap p95 frame gap max jank count cls status
default / homepage-cold 32 -> 32 (0) 64 -> 40 (-24) 97 -> 70 (-27) 47 -> 20 (-27) 16.8 -> 16.8 (0) 183.4 -> 183.3 (-0.1) 4 -> 3 (-1) 0 -> 0 (0) pass
default / long-session-input-lag 48 -> 48 (0) 48 -> 64 (+16) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.7 (-0.1) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-streaming-long 48 -> 48 (0) 64 -> 56 (-8) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.8 -> 33.3 (+16.5) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-call-expand 24 -> 16 (-8) 24 -> 40 (+16) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.7 -> 16.7 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-default-open-heavy-bash 24 -> 24 (0) 24 -> 32 (+8) 64 -> 68 (+4) 14 -> 19 (+5) 33.4 -> 50 (+16.6) 166.6 -> 116.6 (-50) 1 -> 3 (+2) 0 -> 0 (0) pass
default / terminal-side-panel-open 48 -> 56 (+8) 56 -> 64 (+8) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.4 (+0.1) 33.4 -> 50 (+16.6) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-scroll-reading 32 -> 32 (0) 48 -> 48 (0) 0 -> 0 (0) 0 -> 0 (0) 33.4 -> 33.3 (-0.1) 33.4 -> 33.3 (-0.1) 0 -> 0 (0) 0 -> 0 (0) pass
low-end / session-scroll-reading-long 72 -> 80 (+8) 88 -> 88 (0) 135 -> 141 (+6) 154 -> 186 (+32) 33.4 -> 33.4 (0) 133.4 -> 149.9 (+16.5) 6 -> 6 (0) 0.011 -> 0.011 (0) pass
low-end / session-timeline-recompute 200 -> 192 (-8) 224 -> 224 (0) 182 -> 179 (-3) 407 -> 385 (-22) 166.7 -> 166.6 (-0.1) 166.7 -> 166.7 (0) 4 -> 4 (0) 0.397 -> 0.474 (+0.077) warn: cls_delta
low-end / concurrent-shimmer-extreme 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/e2e/session/session-composer-dock.spec.ts`:
- Around line 793-798: The current check uses expect.poll(() =>
responseCalls).toBe(1) which returns as soon as responseCalls hits 1 and does
not guarantee no duplicates afterward; change the assertion to first wait for
the single submission (expect.poll(() => responseCalls).toBe(1)) and then
immediately poll for a short stability window to ensure no further increments
(e.g., expect.poll(() => responseCalls, { timeout: 1000 }).toSatisfy(v => v <=
1)); update the test around the forced clicks that use dock.evaluate, submit,
and firstOption so it first asserts responseCalls === 1 and then asserts
responseCalls stays <= 1 for the window to catch any duplicate submission from
raw DOM clicks.

In `@packages/app/src/pages/session/composer/session-question-dock.tsx`:
- Around line 383-386: The toast fallback currently uses
language.t("session.question.error.invalidPayload") when normalized.detail is
empty, which mislabels unknown errors; update the fallback in
session-question-dock.tsx where showToast(...) is called so it uses a generic
unknown error translation key (e.g.,
language.t("session.question.error.unknown") or
language.t("common.unknownError")) instead of the invalidPayload key, and ensure
the matching translation entry is added to the language catalogs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bfc461c-8c28-4643-9473-ff7c095ea129

📥 Commits

Reviewing files that changed from the base of the PR and between 7742021 and b8732de.

📒 Files selected for processing (3)
  • packages/app/e2e/session/session-composer-dock.spec.ts
  • packages/app/src/pages/session/composer/session-question-dock.test.ts
  • packages/app/src/pages/session/composer/session-question-dock.tsx

Comment thread packages/app/e2e/session/session-composer-dock.spec.ts Outdated
Comment thread packages/app/src/pages/session/composer/session-question-dock.tsx
@Astro-Han Astro-Han merged commit 53008a9 into dev May 21, 2026
26 of 27 checks passed
@Astro-Han Astro-Han deleted the pawwork/fix-question-dock-object-error branch May 21, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Question dock can show [object Object] after answering

1 participant