Skip to content

Restore double Ctrl+C to exit functionality (PR #4900) lost in upstream merge #247

@shuv1337

Description

@shuv1337

Problem

The double Ctrl+C to exit functionality from sst/opencode#4900 was broken during a recent upstream merge. This feature prevents accidental exits by requiring users to press Ctrl+C twice within 2 seconds.

Currently, pressing Ctrl+C when the prompt is empty immediately exits the application, which is frustrating when users accidentally clear their prompt or hit Ctrl+C by mistake.

Expected Behavior

  1. First Ctrl+C press (with empty prompt): Show a warning toast "Press again to exit"
  2. Second Ctrl+C press within 2 seconds: Exit the application
  3. After 2 seconds: Reset the exit attempt counter

Current Behavior

Ctrl+C immediately exits when the prompt is empty, with no warning or confirmation.

Root Cause

During upstream merge, the tryExit function and lastExitAttempt tracking variable were lost from packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx. The code at line ~848-855 now directly calls exit() instead of the protective tryExit() wrapper.

Interestingly, the search.tsx component still has the tryExit implementation (lines 42-59), showing this pattern was preserved in some places but lost in the main prompt.

Acceptance Criteria

  • tryExit() function restored in packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
  • lastExitAttempt variable tracks last exit attempt timestamp
  • Warning toast shows "Press again to exit" on first Ctrl+C
  • Second Ctrl+C within 2 seconds actually exits
  • Timer resets after 2 seconds of no activity
  • Feature documented in script/sync/fork-features.json (already present)

Implementation Details

Reference Implementation (from PR anomalyco#4900)

The original implementation adds these changes to prompt/index.tsx:

// After: const exit = useExit()
let lastExitAttempt = 0

async function tryExit() {
  const now = Date.now()
  if (now - lastExitAttempt < 2000) {
    await exit()
    return
  }
  lastExitAttempt = now
  toast.show({
    variant: "warning",
    message: "Press again to exit",
    duration: 2000,
  })
}

Then change the exit handler from:

if (keybind.match("app_exit", e)) {
  if (store.prompt.input === "") {
    await exit()  // <-- Currently this

To:

if (keybind.match("app_exit", e)) {
  if (store.prompt.input === "") {
    await tryExit()  // <-- Should be this

Files to Modify

File Change
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:669 Add lastExitAttempt variable and tryExit() function after const exit = useExit()
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:848-855 Replace exit() with tryExit() in app_exit keybind handler

Reference: search.tsx Working Implementation

See packages/opencode/src/cli/cmd/tui/component/prompt/search.tsx:42-59 for the working pattern already in the codebase:

let lastExitAttempt = 0

async function tryExit() {
  const now = Date.now()
  if (now - lastExitAttempt < 2000) {
    await exit()
    return
  }
  lastExitAttempt = now
  toast.show({
    variant: "warning",
    message: "Press again to exit",
    duration: 2000,
  })
}

External References

Tasks

  • Add lastExitAttempt variable after line 669
  • Add tryExit() function after lastExitAttempt
  • Update exit handler at line ~850 to call tryExit() instead of exit()
  • Verify useToast hook is imported (already present at line 31)
  • Test double Ctrl+C functionality
  • Run bun test in packages/opencode

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions