fix(tui): guard prompt submit against concurrent invocation#26972
Merged
Conversation
When two `submit()` calls in the prompt component overlap (e.g. a double-pressed Enter), both pass the empty-input guard and both `await sdk.client.session.create(...)`. Only after that await does each call read `inputText = store.prompt.input`. The first call clears the store after sending, so the second call reads `""` and sends an empty prompt to a second freshly-created session — leaving an orphaned session with the user's actual text and a phantom session visible to the user with only an assistant reply. This adds a structural reproducer mirroring the exact shape of the production `submit()` and asserts that no submission may end up with empty text. It fails on the current code path.
Wrap `submit()` in the Prompt component with an in-flight `submitting` flag so overlapping invocations bail out instead of each creating a session and racing the store-clear. The original body is preserved verbatim as `submitInner()`. Without this guard, a double-pressed Enter (or the input's native onSubmit racing another dispatch) produces two sessions: one with the user's text, and one with an empty user message that the title-gen model labels from its own system prompt — leaving the user staring at an assistant reply with no visible question. Updates the regression test added in the previous commit to mirror the guarded shape; it now passes.
leohenon
pushed a commit
to leohenon/opencode-vim
that referenced
this pull request
May 12, 2026
sdeonvacation
added a commit
to sdeonvacation/opencode-x
that referenced
this pull request
May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two
submit()calls inpackages/opencode/src/cli/cmd/tui/component/prompt/index.tsxcan overlap (most commonly a double-pressed Enter). Both pass theif (!store.prompt.input) return falseguard, bothawait sdk.client.session.create(...), and both only readinputText = store.prompt.inputafter that await. The first call finishes, sends its prompt, then clears the store; the second call, now past its await, reads""and sends an empty prompt to a second freshly-created session.The user observes:
Reproduced in a real session in the OpenCode DB: two
sessionrows created 3ms apart, one withtext: "Hello there."and one withtext: "", sharing the same directory.Fix
Wrap
submit()in an in-flightsubmittingflag. The original body is preserved verbatim assubmitInner(); only the guard is new.Tests
packages/opencode/test/cli/tui/prompt-submit-race.test.tsis a structural mirror ofsubmit(). The first commit adds it pointed at the buggy shape (no guard) and fails. The second commit adds the production fix and updates the mirror to match, after which both tests pass.Caveat: this is a structural regression test, not an integration test against the real
Promptcomponent. MountingPromptfor a true E2E test would require building harnesses for ~14 contexts (sync, sdk, project, editor, dialog, toast, command-palette, keymap, tui-config, route, args, local, prompt-history, prompt-stash) plus a fake SDK client. Happy to do that as a follow-up if reviewers want it.Test plan
bun run test test/cli/tui/prompt-submit-race.test.tsfrompackages/opencode(2 pass)bun run typecheckfrompackages/opencode(clean)