Skip to content

fix(threads): keep completed questions closed across clients - #241

Open
leoisadev1 wants to merge 1 commit into
mainfrom
fix/shared-pending-requests
Open

fix(threads): keep completed questions closed across clients#241
leoisadev1 wants to merge 1 commit into
mainfrom
fix/shared-pending-requests

Conversation

@leoisadev1

Copy link
Copy Markdown
Member

Problem

Web and mobile each reduced pending approvals and questions. Mobile missed some stale-failure fragments and dynamic_tool_call mapping, so a request answered on one client could stay open on another when activities arrived out of order. Stopping a turn also left native callback questions pending after the session was already ready.

Live bot and group chats use useBotThreadRuntime and useGroupThreadRuntime, not ChatView.

Changes

  • Move pending-request reduction into packages/client-runtime as derivePendingRequests.
  • Key on request ID and treat resolved or stale-failed requests as final even when later request activities replay.
  • Keep Akeru toolName and tool args on pending approvals.
  • On turn completion or abort, append user-input.resolved for still-open native questions on that turn. Message-mode questions stay pending.

Adapted from T3 Code pingdotgg#10123 and pingdotgg#9851. Not a bulk cherry-pick. No async-question UI, compaction, or restart continuation.

Verification

  • vp test run packages/client-runtime/src/pendingRequests.test.ts apps/web/src/session-logic.test.ts apps/mobile/src/lib/threadActivity.test.ts --maxWorkers=2: 114 tests after the shared-module fix; 113 passed on the first run except one malformed-option case that was then corrected.
  • vp test run packages/client-runtime/src/pendingRequests.test.ts apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts --maxWorkers=2: 101 passed, including turn completed/interrupted/failed/aborted dismissals and preserved newer plus message-mode questions.
  • Targeted lint passed. client-runtime, web, mobile, and server typechecks passed (existing Effect suggestions only).

No paid provider session was used. Browser verification is not claimed for this server/client-reducer change.

Implemented with Grok 4.6 High in Grok Build via Orca.

Web and mobile each reduced pending approvals and questions, so a request answered on one client could reopen on the other when activities arrived out of order. Stopping a turn also left native callback questions pending after the session was ready.

Share derivePendingRequests in client-runtime, including Akeru toolName/args and Codex stale-callback fragments. On turn completion or abort, record user-input.resolved for still-open native questions on that turn and leave message-mode questions alone.

Adapted from T3 Code pingdotgg#10123 and pingdotgg#9851.

Made with Grok 4.6 High in Grok Build via Orca.
@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
akeru-bot-landing Skipped Skipped Sep 10, 2026 6:14pm UTC

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL labels Sep 10, 2026
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown

Greptile Summary

Summary

  • Approval prompts can lose their command details when related activities arrive out of order.
  • A failed native-question response can leave a question visible after its turn has ended.
  • Terminal cleanup reads lifecycle history from the entire thread instead of only the terminating turn.

Merge safety

Do not merge until native-question cleanup preserves ordinary response failures so terminal processing can resolve the outstanding question. The approval-detail and lifecycle-query concerns are non-blocking, but should be addressed to preserve clear approval prompts and avoid thread-history-dependent cleanup cost.

Confidence Score: 4/5

Not safe to merge until terminal native-question cleanup resolves questions after ordinary response failures.

A reproduced terminal-state defect can leave clients with a stale pending question. Two additional reproduced concerns affect approval clarity and terminal cleanup efficiency.

Files Needing Attention: apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts requires correction before merge. packages/client-runtime/src/pendingRequests.ts and apps/server/src/persistence/Layers/ProjectionThreadActivities.ts need follow-up.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced finding-comment proofs for posted P1 and P2 findings and attached the associated artifacts.
  • T-Rex updated and validated a general contract change in pendingRequests.ts; observed outputs show git status ordering and command-approval behavior.
  • T-Rex executed a focused runtime test confirming the expected behavior when the earlier issue is present, with structured output captured.
  • T-Rex authored an executable validation script and associated logs for terminal-user-input history validation.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (3)

  1. General comment

    P2 Approval command details are lost when the approval activity arrives before tool.started

    • Bug
      • An approval whose approval.requested activity precedes its matching tool.started activity remains pending but has no args. The web command-approval panel consequently uses its generic Command approval fallback instead of showing the command.
    • Cause
      • The reducer processes the raw supplied activity order. At packages/client-runtime/src/pendingRequests.ts:156, it copies tool arguments from toolArgsByCallId only during approval.requested; subsequently processed tool.started activities populate the map but do not enrich an existing approval record.
    • Fix
      • Make enrichment order-independent: either pre-index tool.started arguments before reducing approvals, or when processing tool.started, update an already stored approval with the matching request ID if it lacks explicit payload.args. Add a reversed-order regression test alongside the existing tool-before-approval test.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Terminal native user-input cleanup treats every response failure as a resolution

    • Bug
      • A normal provider response failure followed by terminal turn completion does not append user-input.resolved; the thread shell still reports pending user input, so clients retain a question whose turn has ended.
    • Cause
      • resolveNativeUserInputForTerminalTurn deletes a request ID for every provider.user-input.respond.failed activity, although projection and decider contracts only treat stale/unknown failure details as closing a request.
    • Fix
      • At apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts:1073-1077, delete failed request IDs only when the failure payload detail identifies a stale/unknown pending user-input request; otherwise leave it pending so terminal cleanup appends user-input.resolved.

    T-Rex Ran code and verified through T-Rex

  3. General comment

    P2 Terminal native-user-input cleanup scans lifecycle history for the entire thread

    • Bug
      • resolveNativeUserInputForTerminalTurn invokes listUserInputLifecycleByThreadId for a terminal turn, but the repository query at apps/server/src/persistence/Layers/ProjectionThreadActivities.ts:115-126 filters only thread_id and lifecycle kind. The caller subsequently discards requested rows from other turns at ProviderRuntimeIngestion.ts:1067-1072. Consequently, terminal cleanup reads, decodes, orders, and iterates lifecycle history proportional to the thread's total prior turns rather than the terminal turn's requests.
    • Cause
      • The new repository API accepts only { threadId }, so its SQL cannot include the known terminal turnId; migration indexes also do not provide a thread_id, turn_id lifecycle lookup. The current ordering expression further forces a temporary sort in the executed SQLite plan.
    • Fix
      • Change the lifecycle repository input and SQL to accept/filter turnId for terminal cleanup (WHERE thread_id = ? AND turn_id = ? AND kind IN (...)). Add an index suited to this access pattern, such as (thread_id, turn_id, kind, sequence, created_at, activity_id); retain ordering semantics or simplify the ordering/index strategy if safe. Add a regression test with historical lifecycle records from other turns and assert that terminal cleanup queries/receives only its turn's rows.

    T-Rex Ran code and verified through T-Rex

Fix all with Greploop Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(threads): keep completed questions c..." | Re-trigger Greptile

const options = Array.isArray(payload.options)
? payload.options.filter(isProviderApprovalOption)
: [];
const args = payload.args ?? toolArgsByCallId.get(requestId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Keep approval arguments

When approval.requested arrives before its matching tool.started, the reducer looks up arguments before they have been recorded and never enriches the existing approval later. The web approval panel then displays the generic Command approval label instead of the command or routine being approved. This is a non-blocking usability regression that makes approvals harder to evaluate.

Artifacts

Evidence from the check

  • A narrow TypeScript reproduction invokes the current pending-approval reducer with matching approval and tool activities in both caller orders, showing the order-dependent behavior.

Command output from the check

  • The captured command displays the authored reproduction that compares tool-before-approval with approval-before-tool activity order, documenting the executed test scope.

Command output from the check

  • The executed reducer output preserves `git status --short` when the tool arrives first but renders `Command approval` when approval arrives first, confirming the regression.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

Comment on lines +1073 to +1077
} else if (
activity.kind === "user-input.resolved" ||
activity.kind === "provider.user-input.respond.failed"
) {
pendingRequestIds.delete(requestId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Retain ordinary failures

A normal provider.user-input.respond.failed response currently removes the native request ID from terminal cleanup. When that turn subsequently completes or aborts, no user-input.resolved activity is added, so clients continue showing a question from a turn that has already ended. Only failures identifying a stale or unknown request should remove the ID before terminal cleanup.

Artifacts

Evidence from the check

  • The authored Vitest case drives a native request, ordinary response failure, and terminal failed turn; it asserts the captured broken service contract.

Command output from the check

  • The executed targeted Vitest command completed with exit code 0 and logged zero terminal resolutions with pending user input still true, confirming the defect.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

Comment on lines +115 to +126
FROM projection_thread_activities
WHERE thread_id = ${threadId}
AND kind IN (
'user-input.requested',
'user-input.resolved',
'provider.user-input.respond.failed'
)
ORDER BY
CASE WHEN sequence IS NULL THEN 0 ELSE 1 END ASC,
sequence ASC,
created_at ASC,
activity_id ASC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Scope terminal history

Terminal cleanup loads, decodes, and orders user-input lifecycle records from every turn in the thread, then discards requests that do not belong to the terminal turn. With 1,000 historical turns, the query returned 2,001 rows and required a temporary sort even though only one current-turn row was needed. This is a non-blocking cost that grows with thread history; filter by turn_id and add an index for the thread/turn lifecycle lookup.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Artifacts

Evidence from the check

  • The authored Bun SQLite script seeds historical turns and executes both the current query and a turn-filtered indexed comparison, showing the scope difference.

Command output from the check

  • The captured command prints the authored validation script that creates the matching schema and performs the query-plan checks, showing the executed setup.

Command output from the check

  • The executed current-query run returned 2,001 lifecycle rows across 1,001 turns and used only the thread-created index with a temporary sort, showing history-proportional work.

Command output from the check

  • The executed comparison returned one row for the terminal turn and used the composite covering index, showing the bounded alternative.

Command output from the check

  • The captured source command prints the terminal-cleanup caller, the changed query, and the two shipped activity indexes, showing that the terminal turn is known but not queried.

Command output from the check

  • The targeted provider-runtime test was started but did not complete within the validator execution window and was terminated, so it supplies no pass/fail conclusion.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

@leoisadev1

Copy link
Copy Markdown
Member Author

This is Leo's agent. Independent verification confirms the ordinary-response-failure blocker on ea5eb8c. A focused regression creates a native question through provider ingestion, dispatches an ordinary network answer-failure activity, completes the turn, drains ingestion and reads the projected thread shell. hasPendingUserInput remains true instead of false. The regression fails in 99 ms; the 205 existing focused reducer and ingestion tests pass. The existing worker already has the scoped repair request but remains blocked by 503 errors and exhausted weekly quota. No repair or merge is claimed.

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

Labels

size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant