Flutter Process Manager
Sidebar panel that tracks flutter run (and similar) processes — wherever they're running
from, including ones started by an AI coding agent — and lets you drive Hot Reload / Hot
Restart / Quit and watch live logs without touching the terminal.

Two flutter run sessions tracked side by side — one launched on a Pixel 9 (Android), one on
an iPhone simulator — each independently controllable, with live logs streaming underneath.
The panel picked up both without either being started in a terminal it created: here, Claude
Code (right panel) launched the iOS session as a backgrounded task, and the pid-scan path
found it and started tailing flutter logs automatically.
What it detects
Two independent detection paths, combined into one list:
- Terminal-tracked. Any command run in a VS Code integrated terminal whose command line
matches one of
flutterProcessPanel.matchPatterns (default ^flutter run\b), via the
built-in Terminal Shell Integration API
(onDidStartTerminalShellExecution / onDidEndTerminalShellExecution /
onDidCloseTerminal). Works whether you typed the command yourself or an agent did, as
long as shell integration is active for that terminal (VS Code default for
bash/zsh/fish/pwsh).
- Process-scanned (pid-only). Anything with no VS Code terminal at all — e.g. an AI
agent's backgrounded task — fires no VS Code event we can hook. Instead, a
ps scan runs
on activation, on Flutter Process Manager: Refresh Processes, and every 4s while
watching is active (macOS/Linux only), looking for any flutter_tools.snapshot ... run
process not already reachable from a tracked terminal. Only the top-level process is
picked (a nested child matching the same pattern — e.g. a build/install helper — is
skipped, so a signal never lands on the wrong process).
Controlling a process
- Terminal-tracked rows: buttons are driven by
flutterProcessPanel.keymap (default
Hot Reload r / Hot Restart R / Quit q), sent via terminal.sendText. A free-text box
sends anything else (e.g. p for widget inspector, o to switch platform) followed by
Enter.
- Process-scanned rows: there's no terminal to type into, so Reload/Restart/Quit are
sent as OS signals (
SIGUSR1/SIGUSR2/SIGINT — the mechanism Flutter's own tooling
documents for non-interactive control) directly to the process.
- Exception: if the command line contains
--machine, the process is already being
driven by another tool over its own stdin-based protocol (VS Code's own Dart/Flutter
debugger, or any other IDE integration). Sending it an extra raw signal on top of that
has crashed the session in testing — two independent controllers racing on the same
restart path. For --machine rows only Quit is offered; use the owning tool's own
reload/restart controls instead.
Click a terminal-tracked row's header to focus/reveal its terminal (not available for
process-scanned rows — there's no terminal to reveal).
Logs
- Terminal-tracked rows: captured live via
TerminalShellExecution.read().
- Process-scanned rows: only for mobile-ish targets (anything except
chrome/edge/macos/windows/linux/web-server, i.e. Android/iOS
simulators/devices) — a separate flutter logs -d <device> process is spawned (from the
tracked process's resolved cwd) and streamed in. Desktop/web pid-only rows show no logs;
there's no equivalent tailer command for those targets.
- Both paths cap retention at 1000 lines (matching VS Code's own
terminal.integrated.scrollback default), trimming from the oldest line once exceeded.
- Logs render in a read-only Monaco editor (syntax: plaintext, word wrap, VS Code's own find
widget for in-log search) — auto-scrolls to the bottom only if you were already there;
scroll up to read history and it won't yank you back down.
- Click Show Logs to open a row's log viewer, Hide Logs to close it.
Search
The top search box highlights matches, it doesn't filter — non-matching rows stay
visible (dimmed), matching rows get a highlighted border. Searches command text, device,
cwd, terminal name, and PID. Hidden automatically when the current tab has nothing to
search.
Tabs, pause, and cleanup
- Active Processes / History tabs split running vs. exited entries.
- Refresh Processes (toolbar) re-runs the pid-scan immediately instead of waiting for
the next 4s poll.
- Clear Dead Processes (toolbar) drops all exited entries from the list at once.
- Pause Watching / Resume Watching (toolbar, context-sensitive) stops/starts both
detection paths — new terminal commands are ignored and the pid-scan poll stops while
paused; resuming immediately re-scans.
- Exited processes stay listed (grayed out, with exit code) until dismissed or cleared, or
until you switch to the History tab.
Limitation (read this)
- Process-scanned detection needs
ps and is macOS/Linux only — not supported on Windows
(no POSIX signals either), so pid-only rows simply won't appear there. Terminal-tracked
detection is unaffected (shell integration works fine on Windows).
- Process-scanned rows have no free-text/arbitrary-keystroke box (signals only cover
reload/restart/quit) and no live exit-code (exit is inferred next scan when the PID
disappears).
- If a row isn't behaving as expected, check
Flutter Process Manager: Show Log Output —
spawn errors, stderr, and exit codes for the flutter logs child processes are logged
there.
Configuration
Run in dev
npm install
npm run compile
Then press F5 in VS Code (with this folder open) to launch an Extension Development Host
with the panel installed. npm install pulls in monaco-editor, used for the log viewer —
required before the panel will show logs correctly in dev.
Manual test checklist
- [ ] Run
flutter run in an integrated terminal → row appears under Active Processes with
command text, terminal name, and a "running" status pill.
- [ ] Click Hot Reload / Hot Restart / Quit → terminal receives the mapped
keystroke; Quit exits the process and the row moves to History with an exit code.
- [ ] Type something in the free-text box and hit Enter/Send while running → sent followed
by a newline.
- [ ] Click the row header → its terminal is revealed/focused.
- [ ] Click Show Logs → Monaco viewer opens, live-updates, stays pinned to bottom unless
you scroll up.
- [ ] Search box: type a substring of a running command → matching row highlighted,
non-matching rows dimmed (not hidden).
- [ ] Start a
flutter run outside any VS Code terminal (e.g. via an AI agent's backgrounded
task) → appears as a pid-only row within ~4s (or immediately via Refresh
Processes), with Reload/Restart/Quit via signals.
- [ ] Same, targeting an Android/iOS device → Show Logs works via
flutter logs.
- [ ] Same, targeting a desktop/Chrome target → no Show Logs button.
- [ ] Clear Dead Processes → all exited rows disappear at once.
- [ ] Pause Watching → new
flutter run commands aren't tracked; Resume Watching
picks them up again immediately.
- [ ] Close a terminal-tracked terminal directly → its row disappears.
- [ ] Two simultaneous
flutter run instances (different terminals or devices) → both
tracked as separate rows, act independently.
| |