Skip to content

Keep an answered server request answered across a reload - #127

Merged
marmeladema merged 1 commit into
mainfrom
claude/sub-agent-approval-requests-vffexn
Jul 25, 2026
Merged

Keep an answered server request answered across a reload#127
marmeladema merged 1 commit into
mainfrom
claude/sub-agent-approval-requests-vffexn

Conversation

@marmeladema

@marmeladema marmeladema commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes the same defect AR1 already fixed for approvals, left open on the path beside it.

The problem

Answering a server request recorded nothing on the server. A request leaves the pending set only when the harness emits its own ServerRequestResolved, which arrives on the harness's schedule and may never arrive at all. Until then the replayed ServerRequestReceived still reads as outstanding, so a reload rendered the card actionable again — and answering a second time routes a stale id to the harness, which errors.

The change

The answer is recorded against the in-flight turn the moment it is routed (LiveBufferStore::resolve_server_request, called from the ServerRequestResponse handler — respond_server_request now returns the ThreadId so the caller knows where to record it).

Excluding answered requests from pending_server_requests is not sufficient on its own: the request still rides along in accumulated, and replaying that renders an actionable card unless the client is told it was answered. So LiveTurnSnapshot also carries answered_server_requests, and the client seeds that set before replaying accumulated events — exactly the shape answered_approvals already has.

Answered requests are also excluded from the connect bootstrap added in #126, so a latecomer isn't told a thread is waiting on input the user already gave.

Coverage

Server requests had no browser coverage at all, despite real UI: a card, per-question controls, Continue/Cancel, and a resolved state. They do now, driven by a scripted requestUserInput whose harness deliberately never resolves it — precisely the window in question.

  • Answered → reload → comes back resolved, both actions disabled, no error, and the sidebar stops claiming the thread is waiting on input.
  • Unanswered → reload → comes back still actionable, so the fix can't pass by swallowing requests wholesale.
  • answered_server_request_is_not_pending_after_reconnect pins the snapshot contract in Rust, with a harness flag that suppresses resolution.

The answered test was confirmed to fail without the fix — I disabled the server-side recording, rebuilt, and watched it fail (no resolved class, card actionable) while the unanswered test kept passing.

Verification

483 Rust tests passing with every target compiling, fmt --check and clippy --all-targets --all-features -D warnings clean, 30/30 Playwright across 10 consecutive full runs.

One caveat worth stating: during this work I hit a cluster of 3 identical failures in thread-selection.spec.ts (a 10s timeout waiting for the scripted reply) that I could not reproduce in 10 subsequent full runs, including one deliberately launched under build contention to test that theory. I don't have an explanation and I'm not claiming one — flagging it rather than pretending the suite is understood.

Follow-up worth considering

Approvals and server requests are nearly the same concept, and this PR makes that hard to miss — the two now have parallel resolution state, parallel snapshot fields, and parallel client sets. The codebase already blurs the line: MCP tool-call approvals arrive from Codex as requestUserInput server requests and get promoted to approval cards by marker.

The concrete cost today is that the "blocked ≠ busy" work from #125 doesn't apply here — a thread (or sub-agent) waiting on user input still ranks and renders as merely running, and fires no notification:

approval server request
activity rank 3 1 (same as running)
sidebar glyph ! o
sub-agent card "Needs approval" "Running"
notification yes none

Not fixed here to keep this PR to one defect, but it's a small presentation-layer change and probably the right next step.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NhKuYcqLiesiQEDJJ1gBCY


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Answered server requests now remain resolved after browser reloads or reconnects.
    • Resolved requests no longer reappear as pending or actionable.
    • Unanswered requests continue to remain available for user input.
  • Documentation

    • Updated protocol documentation and end-to-end testing guidance for server-request persistence.
  • Tests

    • Added coverage for server-request behavior across reloads and reconnects.

Answering a server request recorded nothing on the server. A request
leaves the pending set only when the harness emits its own resolved event,
which arrives on the harness's schedule and may never arrive at all. Until
then the replayed ServerRequestReceived still reads as outstanding, so a
reload rendered the card actionable again — and answering a second time
routes a stale id to the harness, which errors. That is the same defect
already fixed for approvals, left open on the path beside it.

The answer is now recorded against the in-flight turn the moment it is
routed, and the reconnect snapshot carries answered_server_requests.
Excluding them from pending_server_requests is not enough on its own: the
request still rides along in `accumulated`, and replaying that renders an
actionable card unless the client is told it was answered. The client
seeds the answered set before replaying, exactly as it does for approvals.
Answered requests are also excluded from the connect bootstrap, so a
latecomer is not told a thread is waiting on input the user already gave.

Server requests had no browser coverage at all despite having real UI — a
card, per-question controls, Continue/Cancel and a resolved state. They do
now, driven by a scripted user-input request whose harness deliberately
never resolves it, which is precisely the window in question. Two tests:
an answered request comes back resolved and never actionable, and the
mirror image, an unanswered one comes back still actionable rather than
being swallowed. The first was confirmed to fail without this fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhKuYcqLiesiQEDJJ1gBCY
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b6a5dd2f-ee02-4587-952a-81427f8f2b41

📥 Commits

Reviewing files that changed from the base of the PR and between d79b593 and ddaf25c.

📒 Files selected for processing (12)
  • crates/giskard-proto/src/lib.rs
  • crates/giskard-server/src/bin/giskard-server-replay.rs
  • crates/giskard-server/src/live_buffer.rs
  • crates/giskard-server/src/registry.rs
  • crates/giskard-server/src/routes.rs
  • crates/giskard-server/static/app.js
  • crates/giskard-server/tests/server_requests.rs
  • crates/giskard-server/tests/ui.rs
  • specs/giskard-specification.md
  • tests/e2e/README.md
  • tests/e2e/tests/helpers.ts
  • tests/e2e/tests/server-requests.spec.ts

📝 Walkthrough

Walkthrough

The protocol now carries answered server-request IDs in live-turn snapshots. Server routing records answers immediately, reconnect snapshots exclude them from pending activity, and the UI restores their resolved state after reload. Replay, unit, integration, specification, and Playwright coverage were added or updated.

Changes

Server-request reconnect persistence

Layer / File(s) Summary
Snapshot contract and specification
crates/giskard-proto/src/lib.rs, specs/giskard-specification.md
LiveTurnSnapshot now includes answered_server_requests, with updated protocol documentation and resolution invariants.
Server resolution tracking and routing
crates/giskard-server/src/live_buffer.rs, crates/giskard-server/src/registry.rs, crates/giskard-server/src/routes.rs
Answered request IDs are recorded against active turns, excluded from pending activity, and returned with their associated thread ID from request routing.
Replay scenario and UI reconstruction
crates/giskard-server/src/bin/giskard-server-replay.rs, crates/giskard-server/static/app.js, tests/e2e/tests/helpers.ts, tests/e2e/README.md
The replay harness emits an unresolved input request, while reconnect snapshots restore answered requests as resolved UI cards.
Reconnect and reload coverage
crates/giskard-server/tests/server_requests.rs, crates/giskard-server/tests/ui.rs, tests/e2e/tests/server-requests.spec.ts
Tests verify answered requests are not pending after reconnect and unanswered requests remain actionable after reload.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Routes
  participant HarnessRegistry
  participant LiveBufferStore
  Browser->>Routes: ServerRequestResponse
  Routes->>HarnessRegistry: respond_server_request
  HarnessRegistry-->>Routes: thread_id
  Routes->>LiveBufferStore: resolve_server_request
  Browser->>LiveBufferStore: reconnect snapshot
  LiveBufferStore-->>Browser: answered_server_requests and filtered pending requests
Loading

Possibly related PRs

  • marmeladema/Giskard#126: Both changes update reconnect bootstrap and live-buffer handling for user-facing pending activity.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preserving answered server requests across reloads.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sub-agent-approval-requests-vffexn

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

@marmeladema
marmeladema merged commit e5ba346 into main Jul 25, 2026
5 checks passed
@marmeladema
marmeladema deleted the claude/sub-agent-approval-requests-vffexn branch July 25, 2026 14:16
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