Skip to content

[Bug]: Async questions make a running thread show as Awaiting Input #15258

Closed

Description

@btsouth

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web (also apps/mobile and packages/client-runtime)

Steps to reproduce

  1. Have a Codex thread post an async question (an agentMessage with delivery: "async" and questions). CodexAdapterV2 stores it as a user_input runtime request with responseCapability: { type: "message" }, and the question is designed to stay pending while the agent keeps working.
  2. Don't answer it. Send the thread a normal message instead, or just let the agent continue.
  3. While the run executes commands, look at the thread in the sidebar or the mobile thread list.

I hit this with a delegated Codex child. It asked an async question, got the answer through a normal message 13 seconds later, acknowledged it and kept testing for over an hour. During that time task_status reported running/working and pendingRequestCount was 1. Not answering through the request API is why the question stayed open, and that part is expected.

Expected behavior

A thread whose run is still executing should read as Working, possibly with a separate marker for the open question. "Awaiting Input" or "Input" should mean the run is actually blocked waiting for an answer.

Actual behavior

The row says Awaiting Input (legacy sidebar and status pill) or Input (sidebar v2 and mobile), with no Working indicator or elapsed time. It is also left off the Working shelf. A genuinely blocked question shows exactly the same status, so an agent that's still working looks stalled.

Cause:

  • ProjectionStore builds the shell's pendingRuntimeRequest from the newest pending request but keeps only id, kind and createdAt. responseCapability is dropped (OrchestrationV2PendingRuntimeRequestSummary).
  • presentThreadShell (packages/client-runtime/src/state/models.ts) sets hasPendingUserInput whenever kind === "user_input".
  • resolveSidebarThreadStatus and resolveThreadStatusPill (apps/web/src/components/Sidebar.logic.ts) and resolveThreadListV2Status (apps/mobile/src/features/threads/threadListV2.ts) check hasPendingUserInput before the runtime status.

The thread detail view already makes the distinction. derivePendingThreadRequests marks message-mode questions dismissible, and ChatView keeps its working state from the runtime. Only the list status loses it.

A focused test on 77823bd using presentThreadShell with status: "running" plus a pending user_input request gives:

runtime.status = running, hasPendingUserInput = true
sidebar v2 status = input | legacy pill = Awaiting Input | Working shelf = false
mobile list status = input
blocking question (runtime waiting): sidebar v2 status = input | pill = Awaiting Input
detail view, async:    responseCapability=message, dismissible=true
detail view, blocking: responseCapability=live, dismissible=false

Possible narrow fix: add a flag such as blocking (or the response capability type) to the pending request summary. Only let blocking user_input requests override a running or starting runtime. Async questions would then still show while the run is active, for example as Working with a question badge, and take over the row once the run stops.

Impact

Minor bug or occasional failure

Version or commit

77823bd (main)

Environment

Linux desktop app, Codex provider

Workaround

Answer or dismiss the question through the question UI or t3_pending_request_respond. A normal message to the thread does not clear it.

Activity

  1. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @btsouth for the precise root-cause trace and the focused test output.

    Confirmed on main at 77823bd1. It's expected that the async question stays pending. What's wrong is the status the thread list shows.

    Codex stores an agentMessage with delivery: "async" as a pending user_input request with responseCapability: { type: "message" }, and that request doesn't block the run (CodexAdapterV2.buildUserInputRequestArtifacts). The turn keeps running, and the thread detail view already marks the question as dismissible.

    The shell summary only keeps id, kind, and createdAt (OrchestrationV2PendingRuntimeRequestSummary in threadShellFromProjection), so presentThreadShell sets hasPendingUserInput for every user_input. Several places check that flag before the runtime status:

    • resolveSidebarThreadStatus and resolveThreadStatusPill: sidebar v2 shows Input, the legacy pill shows Awaiting Input, the row drops off the Working shelf, and the elapsed time disappears.
    • resolveThreadListV2Status: mobile shows Input.
    • ThreadNotificationCoordinator: the same transition triggers an "Input needed" toast.
    • resolveThreadAwarenessPhaseV2: Live Activity and the relay report waiting_for_input.
    • canSnooze: the running thread can't be snoozed.

    Likely fix area

    Your narrow fix lines up with the code. Carry whether the request blocks (or its response capability) in the pending request summary, and only let blocking user_input override a running or starting runtime. Once the run stops, an open async question would still read as Input. A separate question marker while Working is optional.

    The existing tests for resolveSidebarThreadStatus ("prioritizes awaiting input over a running runtime") and isSidebarThreadWorking assume blanket priority today, so they would need to split blocking questions from async ones.

    A maintainer will decide on the fix direction. Your workaround is right for now: answer or dismiss the question through the question UI or t3_pending_request_respond.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
  3. moshemiz06 commented on Oct 12, 2026

    @moshemiz06

    Reproduced on 0.0.46-nightly.20261011.2994, Linux/Ubuntu host, Codex provider, using the installed client bundle and read-only inspection of the affected thread's durable state.

    The user reports that the task stays out of the Working area and still says Input, even though there has been no recent question. The screenshot shows the purple question icon and Input badge.

    What I found

    • A user_input runtime request was created at 2026-10-11 23:15:46.573 UTC with responseCapability: { type: "message" }.
    • It is still pending, with resolvedAt: null. The event history has its pending update but no resolved/cancelled update for that request.
    • Its owning provider turn (run 13) completed at 23:26:32 UTC.
    • Six later runs completed. The latest, run 19, completed at 2026-10-12 02:20:34.748 UTC.
    • t3_thread_read reports status: "completed", activeRunId: null, and pendingRequestCount: 1.

    So the badge comes from an older open async question. The latest run is currently completed, so it should not be in Working at this instant. The bug is that this old nonblocking question also excludes the task from Working whenever a later run starts. A lack of recent questions alone does not establish that the old request was answered; I did not find a resolution event and am not claiming that an answer was lost.

    Reproduction from the installed nightly

    I extracted the actual status and Working membership functions from client/assets/useHandleNewThread-qaHmTPtV.js (ut and ve) and executed them in a Node VM. With hasPendingApprovals: false, hasPendingUserInput: true, default interaction mode, and a later run:

    runtime=starting  sidebar=input  workingShelf=false
    runtime=running   sidebar=input  workingShelf=false
    runtime=completed sidebar=input  workingShelf=false
    control without pending question: runtime=running sidebar=working
    

    The Working function exits on hasPendingUserInput before evaluating runtime activity. The installed runtime-DsY2nu5w.js also derives that flag solely from pendingRuntimeRequest?.kind === "user_input", losing the nonblocking/message distinction described above.

    Minimal user flow:

    1. Enable the Working section and have Codex ask an async question.
    2. Leave that question pending and let the run finish.
    3. Send another ordinary prompt that keeps the agent working.
    4. The new run still shows Input and remains in the inbox instead of moving to Working.
    5. Let subsequent runs finish: the old Input badge persists until the request is explicitly handled.

    Expected: while the later run is executing, show Working and place it in Working, while keeping the optional question separately accessible. Preserve normal Input handling for genuinely blocking questions.

    The Working shelf is intentionally status-driven and not a drag/drop destination (Sidebar.logic.ts explicitly rejects it), so dragging the card cannot correct this classification. That explains why it feels impossible to move the task down.

    Validation limit: I verified the existing affected state and reproduced the classification directly from the installed nightly functions. I did not create a fresh UI thread or perform a UI drag: preview_open failed with Preview automation open failed: This host blocks the sandbox T3's browser runs in (AppArmor on Ubuntu 23.10+). No live thread state was changed.

  4. MaximilianMauroner commented on Oct 13, 2026

    @MaximilianMauroner

    Note

    gpt-6.1-sol · Codex responding on behalf of the user

    Additional case: answering a repeated async question leaves the original pending

    Verified in an active T3 Code thread. This adds a request-lifecycle case to the async-question status classification discussed here:

    1. The original async question stayed pending after its originating agent turn ended.
    2. The user asked to repeat the question. A second request was created, and the user answered it. The second request completed.
    3. The original was neither answered nor superseded. t3_thread_read reported pendingRequestCount=1; t3_pending_request_list returned exactly the original request, and t3_pending_request_read confirmed it asked the same decision.

    Before cleanup, the authoritative listing reported activity=awaiting_input, hasPendingUserInput=true, hasPendingApprovals=false, isRunning=true, and statusReason="open because user input is pending". The agent had accepted the user's choice, but the server still held the original question open.

    The primary agent applied the user's already-given answer to that specific duplicate through t3_pending_request_respond. The pending list then returned [], with activity=running, hasPendingUserInput=false, hasPendingApprovals=false, and statusReason="open because session is running". No new product choice, permission approval, or production setting change was involved.

    Proposed workflow fix, not implemented: When explicitly resurfacing the same pending question, reuse its request identity or supersede only that duplicate. Preserve unrelated pending questions and approvals. This request-lifecycle change is separate from the status-display policy discussed in this issue.

    Regression acceptance: Ask an async question, end the turn, repeat it, answer the replacement, and verify no orphan duplicate or “Input needed” status remains. Also verify that two distinct unanswered questions remain pending.

    Direct mobile visual reproduction was not performed, so a mobile cache or rendering defect is unverified. Affected server/mobile versions were not recorded in the supplied evidence. No app source fix or fresh reproduction was performed for this comment.

  5. juliusmarminge commented on Oct 19, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Fixed by #15430, which just landed on main. Pending request summaries now carry a blocking flag, so an async (nonblocking) question keeps a running thread showing Working in the sidebar, status pill, and mobile list, and only switches to Input once the run ends. Blocking questions and secret requests still show Input as before. Thanks @btsouth for the detailed trace.

    The repeated-question lifecycle case mentioned above (answering a re-asked async question leaving the original one pending) isn't changed by this PR. If you still hit that or a stuck Input badge on a current nightly, please open a new issue with the details.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.codexvia-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions