PacketBenchDocs

Troubleshooting#

Most things that go wrong in PacketBench go wrong with an exact string attached — in a terminal pane, in an amber dictation panel, in a health badge, or in a sanitised host-API message. Those strings are the index into this page, because they are the only part of a failure a reader can search for.

This page groups symptoms by the surface that produced them, names the cause, and gives the workaround where one exists. Where a workaround cell reads , there is none, and the row says why.

Terminal panes#

Everything below is what a PTY pane writes into its own xterm buffer. The pane never surfaces a modal for these; if you did not read the pane, you did not see the error.

What you see What happened Workaround
[Session ended] in grey The child process exited, or you killed or restarted the pane (src/hooks/useTerminalSession.ts:322) Restart the pane. This is also written after a crash — see below
Failed to start <CLI>: <message> in red, then Make sure '<cmd>' is installed and on your PATH. in grey The spawn itself failed (src/hooks/useTerminalSession.ts:421) Install the CLI, or pin an absolute path — see the allowlist note
Command '<x>' is not allowed. Allowed commands: [...] The program is not on the PTY allowlist (src-tauri/src/commands/pty.rs:30, raised at :801) Use one of the listed programs. The allowlist is claude, codex, opencode, packetcode, ssh, and the shells
Project path '<x>' is not a valid directory The workspace's projectPath no longer exists on disk (src-tauri/src/commands/pty.rs:841) Repoint the workspace, or recreate the directory
A pane that opens and never paints anything A WebGL canvas was attached while the container was hidden or 0×0 Switch to another workspace and back — the hidden→visible transition disposes and reattaches the addon
On macOS, a CLI that resolves in Terminal is "not found" in the packaged app launchd hands a .app a minimal PATH/usr/bin:/bin:/usr/sbin:/sbin — which contains no Homebrew, npm, or ~/.local/bin PacketBench already reconstructs PATH at startup (src-tauri/src/core/shell_path.rs). If your install directory is unusual, add it to a literal PATH= line in a shell rc file, or pin an absolute path

The allowlist matches on the program name, not the whole command string (src-tauri/src/commands/pty.rs:66, :798). A manually pinned absolute path such as D:\projects\packetcode\bin\packetcode.exe is therefore accepted by its basename, which is the supported way to run a CLI that is installed somewhere PATH does not reach.

A silent pane end is not necessarily a clean exit#

A CLI that crashes on startup renders exactly like one you closed: the pane writes [Session ended] and stops. There is no exit code, no error, and nothing to act on.

The backend does the work and the frontend throws it away. commands/pty.rs reads the child's status and emits a typed PtyExitPayload; the normaliser for it, parsePtyExitPayload, is exported from src/lib/tauri.ts:227 and has no production callers — its only references are in tests. The real listener at src/hooks/useTerminalSession.ts:360 binds listen<unknown> and discards the payload. This is recorded as a P1 defect in backlog.md, not as a design decision.

The live case that found it: the vendored @openai/codex 0.147.0 binary access-violates on startup with 0xC0000005. On Windows a .cmd-wrapped CLI is spawned as cmd.exe /c codex.cmd (src-tauri/src/commands/pty.rs:873-880), so cmd.exe starts successfully, the PTY session is created, the real CLI dies milliseconds later, and the pane closes quietly.

Warning

When a pane ends immediately and you did not close it, run the CLI directly in a terminal before assuming PacketBench is at fault. codex --version reproduces the crash above outside the app. Until the exit payload reaches a listener, that is the only way to tell a broken CLI from a clean exit.

Blank panes were a parser abort, not a missing feature#

xterm 6.0.0's bundled DECRQM handler (requestMode) throws ReferenceError: Can't find variable: i in minified builds, which aborts the entire parse. Any output stream containing a DECRQM query then renders nothing. opencode/opentui sends several on startup, which is why its panes were blank while claude and codex — which do not query — were fine.

PacketBench registers a no-op CSI handler for both DECRQM forms (src/hooks/useXterm.ts:68-69) so the broken built-in never runs. The CLI receives no mode-support reply and handles that gracefully. Implemented; if you still see a blank opencode pane on 0.13.1, it is not this.

SSH remote workspaces#

A remote workspace always launches ssh locally and runs the CLI on the other end, so failures split cleanly into local-spawn failures and remote-shell failures.

What you see What happened Workaround
Command 'x' is not allowed. on a machine with remote workspaces A local pane is trying to launch something unlisted. A remote workspace launches ssh, which is allowlisted Check which pane raised it; the remote path cannot produce this
A remote CLI is "not found" even though it is installed The remote command is not run through a login shell, so a PATH exported from ~/.bash_profile never applies PacketBench prepends $HOME/.local/bin, $HOME/.npm-global/bin, $HOME/.cargo/bin, $HOME/.opencode/bin, the newest nvm Node bin, and /usr/local/bin (src/lib/ssh.ts:10; the sidecar's copy is src-tauri/src/commands/agent_sidecar/supervisor.rs:38). If your CLI is elsewhere, put it in a file a non-login shell reads, or pin an absolute path
Every tool call is slow, on Windows only Windows OpenSSH has no ControlMaster — it needs Unix domain sockets — so every call pays a full handshake (src-tauri/src/core/execution.rs:96, :130) None — expected. On Unix the same code sets ControlMaster=auto with a socket under ~/.packetbench/ssh-cm/ and ControlPersist=60, so only the first call pays
Refusing to launch against <host>: host key not verified. Pin it on the Servers page first. The Flight target's host record carries no fingerprint (src-tauri/src/commands/flight_attempts.rs:248) Settings → Remote Hosts, open the host, use Verify host key, save
An amber banner reading N hosts without a pinned key Those hosts fall back to trust-on-first-use, so a first connect cannot detect an impostor (src/components/views/tools/ServersSettingsCard.tsx:167) Same fix. Existing setups keep working either way
The Memory pane looks like the wrong project Remote scope keys are ssh:<serverId>:<remote path> and never match a local workspace Check the scope chip in the Memory header — it reads <server> · <folder> with a server icon on a remote workspace
Important

The refusal message says "Servers page"; the settings group is labelled Remote Hosts (src/lib/settingsNavigation.ts:122). They are the same card. The string has not been updated to match the label.

Dictation#

Dictation carries more failure machinery than any other surface, because a Bluetooth headset that walks out of range can block inside CPAL indefinitely and a bare spinner is a dead end.

What you see What happened Workaround
An amber panel after 8 s of "Starting…" The microphone has not opened. The panel explains the likely causes and offers Cancel (src/components/views/DictationView.tsx:17) Cancel, then check the device in Settings → Integrations & Data → Dictation
The start gives up and reports a failure after 15 s The store's start watchdog fired, told the backend to drop the device, and reset (src/stores/dictationStore.ts:98) Reconnect the device and retry. Without this the store would keep isStarting true forever and push-to-talk would go permanently dead
An amber panel after 90 s of "Transcribing…" Large models on long recordings genuinely take this long. The panel says so and offers Cancel (src/components/views/DictationView.tsx:18) Wait, or Cancel. Escape always reaches the backend
Transcription is abandoned with a timeout The transcribe watchdog fired. Its budget is 20× the recorded length, with a 90 s floor for cold model load and a 15-minute ceiling (src/stores/dictationStore.ts:99-100, :122-126) Use a smaller Whisper model, or record shorter takes
Recording stops on its own and transcribes what it has The maximum recording length was reached — 300 s by default, clamped by the backend to between 10 s and 1,800 s (src-tauri/src/commands/dictation/config.rs:59, :78) Raise the ceiling in Dictation settings. The clamp bounds retained PCM even if a key release is missed
The device dies mid-recording Dictation stops and transcribes anyway, so the words already spoken survive; the device error comes back as a warning on the result None needed — this is the salvage path
The device dies while opening The in-flight open is abandoned and a salvage stop runs (src/stores/dictationStore.ts:209-244) Reconnect and retry
A non-fatal dictation:warning stall notice The capture has delivered no new frames for 60 emitter ticks, roughly two seconds (src-tauri/src/commands/dictation/audio.rs:80, :741) Keep going. The capture is deliberately not torn down — a stall can recover, and tearing it down would cost you the buffer
The capture was too short to transcribe. Hold the dictation key until the waveform moves. The capture did not reach the minimum length (src-tauri/src/commands/dictation/audio.rs:930) Hold the key longer
A wedged "Transcribing…" spinner Anything above that did not resolve Escape. Cancel always reaches the backend and always resets locally; a cancel with nothing in flight is a state reset and will not wipe a transcript you are still reading

The stall watch counts frames, not loudnessStallWatch::observe compares the cpal frame counter against the previous tick and never reads amplitude (src-tauri/src/commands/dictation/audio.rs:92-125). Speaking quietly does not trip it; a headset that goes silent without raising a stream error does, and so does a capture that never started at all, because the counter sits at zero from the first tick. It fires once per capture rather than once every 33 ms.

MCP servers#

Diagnose on a configured server spawns it, performs the MCP handshake, calls tools/list, and shuts it down. It reports one of four states, and those four are not four grades of the same thing. The last two rows are errors raised before any probe runs.

Badge What it means Workaround
connected Handshake and tools/list both succeeded. Latency and the tool list are shown (src-tauri/src/commands/mcp.rs:326)
degraded Checked, and unhealthy. The process started but the handshake or listing failed; the error text and a measured latency are shown (:341) Read the message — it is the server's own error, not PacketBench's
failed The process could not be spawned at all, or the entry's type is not a transport PacketBench recognises (:280, :352) For an unknown transport the message names the value it read and lists stdio, http, sse. That is a typo in your config, not a network problem
notProbed Health unknown. Nothing was sent to the server, so nothing was measured (:263) None — no local probe exists for this transport
MCP server is not configured in the selected scope The name does not exist in the global/project scope you are diagnosing (:240) Switch scope, or re-add the server
MCP stdio server is missing a command A stdio entry with no command field (:295) Fix the entry in ~/.claude/settings.json or the project .mcp.json

notProbed is not a failure#

The doctor is a local stdio prober by construction, and this build has no HTTP MCP client to probe with. An http or sse server therefore reports notProbed and carries no latency, because a number there would imply something was timed.

The distinction is load-bearing and is pinned by a test (src-tauri/src/commands/mcp.rs:604). Every non-stdio server used to be reported as degraded without a single byte being sent to it. degraded reads as "we checked and it is unhealthy", so every healthy remote server looked broken — which teaches you that the indicator means nothing and to ignore a real degraded when one appears. A malformed type is separated out for the same reason: it used to be swept into the same "remote, not probed" answer, which sent you looking for a network problem instead of a typo.

Git hosts#

Host API errors are sanitised before they reach you. The raw response body is logged, never displayed, because it can echo tokens and private repository data (src-tauri/src/core/git_host.rs:931).

Status Message Workaround
401 <host> API error 401: unauthorized — check your <host> token Re-enter the token in Settings → GitHub
403 … forbidden — you may lack permissions or be rate-limited Check the token's scopes first; a rate limit clears on its own
404 … not found — the resource may not exist or may be private Usually a missing scope on a private repository rather than a wrong URL
422 … validation failed — check your request parameters The host rejected the payload. Nothing was written
429 … rate limited — try again later Wait
Other 4xx … client error
5xx <host> API error <code>: <host> server error — try again later Wait

<host> is resolved from the URL that actually answered: api.github.com becomes GitHub, any other host becomes its own hostname, and a hostless URL becomes Git host (src-tauri/src/core/git_host.rs:920-929).

Note

Earlier documentation states that the prefix reads "GitHub API error" for Gitea responses too. That was true when the label was hardcoded in commands/github.rs; it is not true at 0.13.1. host_label_from_url was moved into core/git_host.rs so the agent tools in core/tool_github.rs could reuse it, and it names the host that answered. A Gitea instance at git.example.com now produces git.example.com API error 401: unauthorized — check your git.example.com token.

Installers and first launch#

Nothing PacketBench ships is code signed. Every symptom in this table follows from that one fact — see Release status for what is and is not configured.

What you see What happened Workaround
Windows SmartScreen: "Windows protected your PC" The installer carries no Authenticode signature. src-tauri/tauri.conf.json has no bundle.windows block at all More info → Run anyway. Check the installer's SHA-256 against the table in CHANGELOG.md first — that is the only integrity check available
macOS refuses to open the .app on first launch The bundle is unsigned and un-notarized, so Gatekeeper blocks it. bundle.macOS carries only minimumSystemVersion System Settings → Privacy & Security. Right-click → Open no longer works for a downloaded unsigned app on current macOS
A locally built .app is quarantined Same cause, local build xattr -cr /Applications/PacketBench.applocal testing only. Never ship this instruction to anyone downloading a build
A clean Mac fails the build inside whisper-rs-sys The dictation module build-depends on CMake, which does not ship with the Xcode Command Line Tools brew install cmake. It is mandatory, not optional
A standalone target/release/packetbench.exe reports the sidecar down The per-triple Node binary was never fetched. src-tauri/build.rs:17 emits a cargo warning, not an error, so the build succeeds with a dead sidecar Run pnpm fetch-node and rebuild. Installs made through the MSI or NSIS bundle are unaffected — the bundler places the runtime for you
The app launches but a route shows stale or missing UI after an upgrade from a PacketADE build WebView2 keys its profile by bundle identifier, and the 2026-08-26 rename moved it from com.packetade.desktop to com.packetbench.desktop. Twelve localStorage keys, including unsent composer drafts, are stranded in the old profile None. The old profile stays on disk but nothing reads it. This is an accepted consequence of the rename; upgrades between two PacketBench versions are unaffected
  • Release status — why nothing is signed, and what has actually been proved on a packaged install.
  • Install & first run — building from source, which is the path these symptoms assume.
  • Workspaces & terminals — the pane, shell and session model behind the terminal table.
  • SSH remote workspaces — host pinning, the remote PATH prefix, and what is gated off remotely.
  • Dictation & analytics — capture, delivery safety, and the settings each timeout reads.
  • MCP hub — trust profiles, and which agent rows can use a server at all.
  • Where data lives — the files to inspect when a symptom looks like lost state.