SLOPSHOPPER

lazygit-popup

alt+g opens lazygit in a tmux popup; ESC (or q) quits lazygit and closes it

newbandtoastprocess
★ 1v0.1.0MITupdated 2026-10-08PedroLaRosa/claude-code/mods/lazygit-popup
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · lazygit-popup
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⟨Claude Code's own drawing⟩
README

lazygit-popup

A Claude Code mod: press alt+g and lazygit opens in a popup over your Claude Code pane. Press ESC (or q) to quit lazygit and the popup closes.

lazygit-popup demo: alt+g opens lazygit over Claude Code, ESC closes it

Why

I was fed up with opening a new terminal, or switching to different software, just to see a git diff. So I used Claude Code mods to fix it: lazygit opens in the same pane, right over Claude Code.

Requirements

  • Claude Code with mods (built on 2.1.291): the claude CLI, for claude plugin install
  • tmux 3.2+: Claude Code must run inside it; display-popup needs 3.2
  • lazygit, on your PATH: the mod calls lazygit -cd and lazygit -ucf; a release recent enough to support quitOnTopLevelReturn, or ESC won't close the popup
  • A keybinding: "alt+g": "strip:jump4" in ~/.claude/keybindings.json (see Install)
  • A terminal that sends Alt: Linux terminals do by default. On macOS, set Option to act as Alt (see Install)

Works on macOS, Linux and Windows via WSL: anywhere tmux runs. Native Windows has no tmux, so run Claude Code inside tmux in WSL.

Install the tools with brew install tmux lazygit on macOS, or your package manager on Linux/WSL.

Install

claude plugin marketplace add PedroLaRosa/claude-code
claude plugin install lazygit-popup@pedro-la-rosa-claude-code

A mod can't ship keybindings, so bind the key yourself. Add this to the "Global" bindings in ~/.claude/keybindings.json:

"alt+g": "strip:jump4"

Then run /reload-plugins.

On macOS, make your terminal send Option as Alt (iTerm2: Profiles → Keys; Terminal.app: Use Option as Meta key; kitty: macos_option_as_alt yes).

How it works

  • The key: mods can't read keys directly. The mod draws a hidden button above the prompt tied to Claude Code's built-in strip:jump4 action, and your keybinding fires it.
  • The popup: the button runs tmux display-popup -E lazygit in the session's directory. With -E, the popup closes when lazygit exits.
  • ESC quits: the mod writes quitOnTopLevelReturn: true to claude-popup.yml in lazygit's config dir and loads it on top of your own config.yml, for the popup only. lazygit runs everywhere else are unchanged.

Hack on it

git clone https://github.com/PedroLaRosa/claude-code ~/claude-code
claude --plugin-dir ~/claude-code/mods/lazygit-popup

To load it in every session, add the folder to CLAUDE_CODE_PLUGIN_DIRS in the env block of ~/.claude/settings.json. Popup size is the -w / -h pair in hooks/register.tsx. Check changes with claude plugin validate . and claude plugin test .

Limits

  • tmux only. Outside tmux, alt+g shows a "needs tmux" toast. On Windows, that means using WSL.
Source 1 files
hooks/register.tsx 52 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// alt+g opens lazygit in a tmux popup over this pane, in the session's directory.
4// The popup closes when lazygit exits, and in the popup ESC at lazygit's top level
5// exits it (quitOnTopLevelReturn, layered over your own config.yml for the popup only).
6// A key reaches a mod only through a Button in the band above the prompt naming an
7// engine action, so ~/.claude/keybindings.json binds alt+g to strip:jump4 and a
8// hidden Button there takes it.
9
10// ponytail: tmux only; add a new-terminal-window fallback if you run Claude outside tmux
11async function openLazygit($: EngineInterface) {
12  const pane = await $.env.get('TMUX_PANE')
13  if (!pane) return $.ui.toast('lazygit-popup: needs tmux')
14
15  const dir = (await $.process.run(['lazygit', '-cd'])).stdout.trim()
16  const overlay = `${dir}/claude-popup.yml`
17  await $.fs.write(overlay, 'quitOnTopLevelReturn: true\n')
18  const main = `${dir}/config.yml`
19  const configs = (await $.fs.exists(main)) ? `${main},${overlay}` : overlay
20
21  // display-popup holds the call until lazygit exits; the popup outlives the call
22  // (timeout or reload), so nothing waits on it.
23  void $.process
24    .run(
25      ['tmux', 'display-popup', '-E', '-w', '90%', '-h', '90%', '-t', pane, '-d', await $.session.cwd(), 'lazygit', '-ucf', configs],
26      { timeoutMs: 600_000 },
27    )
28    .catch(() => {})
29}
30
31export const register: Register = on => {
32  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
33    // The band is shared: what draws there beneath stays, the Button hides beside it.
34    const below = await next(e)
35    const { Box, Button } = $.ui.resolve(e)
36
37    return (
38      <Box flexDirection="column">
39        {below}
40        <Box display="none">
41          <Button
42            key="lazygit"
43            label="lazygit"
44            action="strip:jump4"
45            onPress={() => openLazygit($).catch(err => $.ui.toast(`lazygit-popup: ${err}`))}
46          />
47        </Box>
48      </Box>
49    )
50  })
51}
52