Skip to content

feat(cli): support Ctrl-Z suspension#17630

Closed
brtkwr wants to merge 1 commit intogoogle-gemini:mainfrom
agentic-utils:main
Closed

feat(cli): support Ctrl-Z suspension#17630
brtkwr wants to merge 1 commit intogoogle-gemini:mainfrom
agentic-utils:main

Conversation

@brtkwr
Copy link
Contributor

@brtkwr brtkwr commented Jan 27, 2026

  • Restores Ctrl+Z functionality to suspend the CLI process.

  • Resolves keybinding conflicts and refines suspend behavior.

Fixes #5018

Summary

Details

Related Issues

How to Validate

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

@brtkwr brtkwr requested review from a team as code owners January 27, 2026 06:07
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @brtkwr, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the CLI's usability by enabling the standard Ctrl+Z shortcut for process suspension. It ensures that the terminal's state is correctly preserved and restored when the application is backgrounded and subsequently brought back to the foreground, providing a more integrated and familiar experience for users accustomed to typical shell interactions.

Highlights

  • Ctrl+Z Suspension: Introduced functionality to suspend the CLI process using the Ctrl+Z key combination, allowing users to temporarily background the application.
  • Terminal State Management: Implemented robust handling of terminal state during suspension and resumption, including managing raw mode, cursor visibility, mouse events, and Kitty keyboard protocol to ensure a seamless user experience.
  • Keybinding Configuration: Added a new SUSPEND command to the CLI's keybinding configuration, mapping it to Ctrl+Z and updating relevant command descriptions and categories.
  • Testing and Documentation: Included a new dedicated test file to validate the suspend and resume behavior, and updated the keyboard shortcuts documentation to reflect the new Ctrl+Z functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for suspending the CLI process using Ctrl+Z. While the overall implementation is robust, a high-severity code review comment has been raised regarding a type assertion in AppContainer.tsx that bypasses TypeScript's type safety, suggesting a more robust typing approach.

enableMouseEvents();
disableLineWrapping();
app.rerender();
(app as unknown as { rerender: () => void }).rerender();
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The type assertion (app as unknown as { rerender: () => void }).rerender(); is used here to call the rerender method. While functional, this bypasses TypeScript's type safety. It would be more robust to ensure that the app object returned by useApp() is correctly typed to include the rerender method, or to import useApp from a version that includes it. This helps maintain type safety and code clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

@gemini-cli gemini-cli bot added priority/p2 Important but can be addressed in a future release. help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! labels Jan 27, 2026
@brtkwr brtkwr force-pushed the main branch 2 times, most recently from 1778767 to 254426d Compare January 27, 2026 10:06
@bdmorgan bdmorgan added the area/core Issues related to User Interface, OS Support, Core Functionality label Jan 27, 2026
@brtkwr
Copy link
Contributor Author

brtkwr commented Jan 28, 2026

Just rebased the PR and ran all the tests locally which appear to be passing.

@gemini-cli gemini-cli bot added the 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. label Jan 28, 2026
Copy link
Collaborator

@scidomino scidomino left a comment

Choose a reason for hiding this comment

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

This will probably create a giant trap for users if they are already used to hitting ctrl+z to undo. What I suggest is that do this in stages.

First, we support ctrl+z but you have to hit it twice and after you hit it the first time we pop up a message saying something like press "ctrl-Z again to suspend, use CMD-Z to undo". Then after running with that for a couple weeks we can remove the toast and just suspend on the first ctrl+z.

setCtrlDPressCount((prev) => prev + 1);
return true;
} else if (keyMatchers[Command.SUSPEND](key)) {
if (process.platform !== 'win32') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

AppContainer is gigantic. Please move this into another file. Have Gemini do a thorough search to see if there are any files it makes sense to move this code to before deciding to create a new one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed - moved suspend behavior into a dedicated useSuspend hook to keep AppContainer smaller.

}

const MAC_ALT_KEY_CHARACTER_MAP: Record<string, string> = {
const MAC_ALT_KEY_CHARACTER_MAP: Record<
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've broken this part out into a separate PR. Let's merge mine first and then you won't even have to touch this file #17800

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the update, scidomino! That sounds like a good plan. If your PR is merged first, then @brtkwr can rebase this branch to remove these changes from packages/cli/src/ui/contexts/KeypressContext.tsx.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Amazing thanks for doing that!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated - rebased and synced with upstream so this PR no longer carries the extra keypress-context changes from before #17800.

@scidomino
Copy link
Collaborator

Actually, I will make the toast message part of my PR so hold off till that is merged.

@scidomino
Copy link
Collaborator

I merged my changes. Sorry if it's a pain but it should make your PR smaller ultimately. You just need to impl the "press ctrl+z twice" logic and the actual suspension. ping my handle in a comment when you are ready for review.

@brtkwr brtkwr force-pushed the main branch 2 times, most recently from fedb3ae to f0b1e13 Compare February 2, 2026 22:06
@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 2, 2026

I merged my changes. Sorry if it's a pain but it should make your PR smaller ultimately. You just need to impl the "press ctrl+z twice" logic and the actual suspension. ping my handle in a comment when you are ready for review.

I've addressed the changes that you requested.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 2, 2026

Unfortunately, it has made the PR slightly larger, not smaller 🙃

@brtkwr brtkwr force-pushed the main branch 11 times, most recently from 18c4cf0 to 71af62c Compare February 2, 2026 23:00
@brtkwr brtkwr requested a review from scidomino February 7, 2026 08:42
Copy link
Collaborator

@scidomino scidomino left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. I left some comments.

}
if (ctrlZPressCount > 1) {
setCtrlZPressCount(0);
if (process.platform !== 'win32') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we only clean up if we're not in windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point - i added an explicit windows guard with a warning so cleanup/suspend logic only runs on supported platforms.

if (ctrlZPressCount > 1) {
setCtrlZPressCount(0);
if (process.platform !== 'win32') {
// Cleanup before suspend
Copy link
Collaborator

Choose a reason for hiding this comment

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

we have a private cleanupOnExit method in TerminalCapabilityManager, make it an exported function and call it here. If you need to add the "Show cursor" command to it, do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed - exported terminal cleanup (cleanupTerminalOnExit) from TerminalCapabilityManager and reused it from suspend handling.

}
setRawMode(true);

terminalCapabilityManager.enableKittyProtocol();
Copy link
Collaborator

Choose a reason for hiding this comment

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

use terminalCapabilityManager.enableSupportedModes(). We don't want to enable this if it's not supported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed - resume path now calls terminalCapabilityManager.enableSupportedModes().

}
}

disableKittyProtocol() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove these unless you have a good reason not to use enableSupportedModes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done - removed the dedicated kitty enable/disable helpers and switched to enableSupportedModes on resume.

@@ -0,0 +1,460 @@
/**
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is nothing GCLI loves more than introducing new test files for classes that already have test files :D

Can you move this test into AppContainer.test.tsx? Only keep it separate if it leads to less overall code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed - moved suspend coverage into AppContainer.test.tsx and removed the separate AppContainer.suspend.test.tsx file.


beforeEach(() => {
vi.resetAllMocks();
vi.stubEnv('GOOGLE_CLOUD_PROJECT_ID', '');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these changes necessary? I can run the tests just fine without them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed - dropped those setup-test env stubs to keep this PR scoped to suspend behavior.

@scidomino
Copy link
Collaborator

When you've addressed these ping me by name with the @ symbol so I get to it right away.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 10, 2026

@scidomino i addressed all open review comments and pushed updates to this PR. ready for another pass when you have a minute.

@brtkwr brtkwr force-pushed the main branch 3 times, most recently from 53e738d to be3e79e Compare February 10, 2026 08:35
@scidomino
Copy link
Collaborator

I see there are merge conflicts. I will attempt to resolve them for you.

@scidomino
Copy link
Collaborator

Looks like you beat me to it.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 10, 2026

Looks like you beat me to it.

👯‍♂️

Copy link
Collaborator

@scidomino scidomino left a comment

Choose a reason for hiding this comment

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

When I suspend it, it doesn't clear the screen, and it leaves me in kitty mode (if it was enabled). And when I unsuspend it, it doesn't repaint the whole screen

after suspending:
Image

After unsuspending:
Image

this is happening on both ghostty and mac terminal.

Copy link
Collaborator

@scidomino scidomino left a comment

Choose a reason for hiding this comment

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

see above

@scidomino
Copy link
Collaborator

Heads up: #18670 is about to be merged which duplicates some of the work you did here. I think it may have also solve some of the issues here because it has protections against registering the same listener more than once.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 11, 2026

@scidomino addressed the suspend/resume issues from your latest review in 822bfe1:

  • exit alternate screen + re-enable line wrapping before SIGTSTP, and clear screen
  • do synchronous terminal mode cleanup before suspend so kitty/modifyOtherKeys are disabled
  • on SIGCONT, re-enter alternate screen, restore supported modes, and force a full repaint
  • added focused coverage in useSuspend.test.ts for suspend/resume + windows guard

please take another look when you get a chance.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 11, 2026

Collaborator

When I suspend it, it doesn't clear the screen, and it leaves me in kitty mode (if it was enabled). And when I unsuspend it, it doesn't repaint the whole screen

after suspending: Image

After unsuspending: Image

this is happening on both ghostty and mac terminal.

I see the same behaviour in other terminal based coding agents. why does it need to clear the screen on suspend?

@scidomino
Copy link
Collaborator

Ok. Approved. I will attempt to resolve the branch conflicts

@scidomino
Copy link
Collaborator

I don't seem to have permission to push to your fork so you will have to resolve the merge conflicts. Sorry.

@brtkwr
Copy link
Contributor Author

brtkwr commented Feb 11, 2026

@scidomino rebased

@scidomino
Copy link
Collaborator

Uhg. More conflicts. Ok. I am going to clone this and see if I can merge it myself. Sorry for all these delays.

@scidomino
Copy link
Collaborator

duplicated to #18931

@scidomino
Copy link
Collaborator

merged as part of #18931

@scidomino scidomino closed this Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. priority/p2 Important but can be addressed in a future release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Ctrl-Z suspension

3 participants