Troubleshooting
Symptom, cause, fix. Several entries end in “that does nothing on this machine” — where that is the answer, it is written as the answer.
Every failure on this page is one whose cause is visible in the app’s own code. Where a mechanism is silent by construction — a check that fails without a message, a toggle that reads On while doing nothing — that is said plainly, because the missing message is the reason you are here.
Start with the log
DeckSpace appends one line per event to a plain text file. It is the only place several of these failures leave a trace.
| Platform | Log file |
|---|---|
| macOS | ~/Library/Logs/DeckSpace/jarvis.log |
| Linux | $XDG_STATE_HOME/deckspace/jarvis.log, defaulting to ~/.local/state/deckspace/jarvis.log |
| Windows | No dedicated location. The path is derived from HOME, which Windows normally does not set, so the file lands in the system temp directory under DeckSpace-logs. |
Embedded newlines are collapsed to ⏎, so every event stays on one line and grep works.
An agent CLI is not detected
Symptom. claude --version works in your terminal, but the CLI shows up grey in Detect installed AI CLIs and DeckSpace will not launch it.
Why. A GUI app launched from Finder or the Dock inherits launchd’s environment, not your shell’s. The minimal PATH it gets has no nvm, asdf, mise, Homebrew or cargo shims in it.
DeckSpace already works around this. Once per app launch it runs $SHELL -lic 'printf %s "$PATH"', merges the result with the directories below, caches the whole thing, and uses it for every pane and every probe.
| Always searched, whatever your shell says |
|---|
~/.local/bin · ~/.grok/bin · ~/.opencode/bin · ~/.cargo/bin · ~/.bun/bin · ~/.npm-global/bin · ~/.deno/bin · ~/.volta/bin · ~/.local/share/mise/shims · ~/bin · /opt/homebrew/bin · /usr/local/bin · /usr/bin · /bin |
Three things break that workaround:
| Cause | What you see | Fix |
|---|---|---|
Your login shell takes longer than 1.5 seconds to print its PATH. The query is killed at that cap so a slow .zshrc can never block the first pane. | Only the fixed list above is searched. Anything installed elsewhere is invisible. | Register the CLI by absolute path, or move it onto the fixed list. |
| The CLI installed itself somewhere neither your shell nor the fixed list mentions. | Same. | Same. |
| You installed the CLI while DeckSpace was already running. | Still not found. The merged PATH is computed once and cached for the life of the process. | Quit and relaunch DeckSpace, then scan again. |
Register it by absolute path. Settings → Custom agent CLIs takes a name, a key, and a binary — the binary field accepts either a bare command or a full path such as /usr/local/bin/goose. An absolute path is checked directly and skips PATH resolution entirely.
The scan resolves the binary and then runs <path> --version with a hard 3-second cap, because some CLIs hang waiting for a TTY. A CLI that hangs still counts as installed — it just shows no version string. That is cosmetic and does not affect launching.
An export ANTHROPIC_API_KEY=… in ~/.zshenv is invisible to a GUI app for the same reason. DeckSpace copies missing variables out of your login shell with env -0, under the same 1.5-second cap, and only for keys the app does not already have — PATH, PWD, OLDPWD, SHLVL and _ are always skipped. If your shell is slow to start, nothing is copied and the agent will fail to authenticate with no explanation on screen.
A pane opens on walk-away and sits there
Symptom. Walk-away is on, the pane launches, the agent starts — and then stops at its own permission prompt and waits for a human who has left.
Why. DeckSpace does not implement autonomy. It passes the flag your CLI publishes for it, and five of the built-in eleven publish none.
| Agent CLI | Walk-away flag |
|---|---|
| Aider | none — the toggle does nothing |
| Cursor Agent | none — the toggle does nothing |
| Droid | none — the toggle does nothing |
| Copilot CLI | none — the toggle does nothing |
| Amp | none — the toggle does nothing |
Fix. Use one of the six that do have a flag — see What walk-away does for the full table — or register the CLI again under Settings → Custom agent CLIs and fill in the Walk-away flag field yourself. A custom agent with that field empty behaves exactly like the five above.
An agent’s command was refused
Symptom. The agent runs something ordinary and gets back a red line beginning ⚠ DeckSpace, or a plain Operation not permitted.
These are two different mechanisms with two different fixes. Read which message you got.
“DeckSpace guard” / “DeckSpace blocked a destructive git”
Agent-driven panes get OPX_GUARD=1 and a shim directory at the front of PATH holding three files: rm, git and sudo. The shims are inert without that variable, so a pane you typed into yourself is unaffected. They parse arguments rather than matching text, so flag spellings do not slip past.
| What the agent ran | What happened |
|---|---|
rm -r, rm -rf, rm -rfv, rm --recursive | Targets moved to ~/.opxspace-trash, not deleted. Yellow warning, exit 0 — the agent believes it succeeded. |
A recursive rm where some target could not be moved | Nothing is deleted. Red warning, exit 1. |
git reset --hard | Refused, exit 1 |
git clean -f / --force | Refused |
git push -f, --force, --force-with-lease, or a +ref refspec | Refused |
git checkout ., git checkout -f | Refused |
git restore — with any arguments at all | Refused |
git switch --discard-changes / -f | Refused |
git branch -D / --delete | Refused |
git stash drop, git stash clear | Refused |
git worktree remove | Refused |
git update-ref -d | Refused |
git reflog expire / delete | Refused |
git filter-branch, git filter-repo | Refused |
git gc --prune=now | Refused |
sudo — anything | Refused |
Fix. The refusal message prints the absolute path of the real binary. Run the command yourself in a pane you opened by hand, or call that absolute path. Deleted work is not gone: look in ~/.opxspace-trash first.
git restore is the entry that surprises people. It is refused unconditionally, including harmless forms like git restore --staged <file>, because the shim keys on the subcommand and not on the flags after it.
“Operation not permitted”
That is the OS sandbox, not DeckSpace. An agent launched into its own pane runs inside a write fence: reads, execution and network are free, all file writes are denied, and writes are then re-allowed in a short list of places.
The fence covers agents DeckSpace starts — in their own pane, in a pane it opens for you (swarm builders, Maestro workers, Kanban runs), and work it runs with no pane at all. A terminal you opened yourself is deliberately not fenced, so if you see this error in an agent pane and not in your own shell, that is why. The full table is on the walk-away page.
| Writable inside an agent pane |
|---|
| The workspace it is working in |
/private/tmp, /private/var/tmp, /private/var/folders, /dev |
~/.claude, ~/.codex, ~/.gemini, ~/.config, ~/.cache, ~/.local/state, ~/.npm, ~/.opxspace-trash |
Everything else is refused by the kernel — whether the agent writes directly, shells out, uses an absolute path, or runs a script. Another repo on the same machine is outside the fence.
Fix. Move the work into the workspace, or turn Confine agent writes off under Settings → Autonomy for the run that legitimately needs to write elsewhere.
There is no sandbox on this machine
| Platform | Backend | Condition |
|---|---|---|
| macOS | sandbox-exec | available when /usr/bin/sandbox-exec exists |
| Linux | bwrap | available only if bwrap --version succeeds |
| Windows | — | always none |
When no backend exists, DeckSpace still opens the pane. The argv is passed through untouched, OPX_GUARD=1 is still set, and the Confine agent writes toggle still reads On. Nothing on screen contradicts you.
The one place that tells the truth is the sentence under that toggle in Settings → Autonomy. With a sandbox it names it. Without one it says this machine has no sandbox available and agents can write anywhere you can.
On Windows this is not a configuration problem. There is no fence to switch on.
The app never offers an update
DeckSpace checks a signed manifest at https://deckspace.dev/deckspace/updates/latest.json — 15 seconds after launch, every 4 hours, and whenever the window regains focus. Automatic checks are floored at one per 30 minutes. When one lands, a green Update now banner appears at the top of the app; nothing installs on its own.
There are four reasons you might never see it, and none of them produce a message.
| Cause | Why it is invisible |
|---|---|
| The endpoint is unreachable — offline, blocked, DNS | An automatic check swallows its error. There is no toast, no badge, nothing in the banner area. |
| There is no Check for updates button | The store has a manual check that reports both outcomes, including You’re on the latest version.. Nothing in the shipped UI calls it, so that path is unreachable and you cannot force a check from inside the app. |
| Background checks are switched off | The store reads deckspace.updates.disabled from browser storage and returns early when it is "1". There is no on-screen toggle that writes that key, so if it is ever set, nothing in Settings will show you it is set. |
| You are looking at a detached panel window | The check cadence is started once, from the main window only. A detached panel never checks and never shows the banner. |
Fix. Quit, relaunch, and leave the main window focused for a minute. If no banner appears, compare the version chip beside the DeckSpace wordmark in the top-left of the window against Updating and install the current build by hand if they differ. Once the banner does appear, one click downloads it, verifies the update signature, installs and relaunches.
The phone remote answers 403
Two entirely different refusals share that status code, and they send you to two different screens. The error.code in the response body is what tells them apart.
| Code | What it means | Fix |
|---|---|---|
forbidden_scope | The device was not paired with the scope this route needs. The message names it: this route needs the order scope. The five scopes are monitor, order, approve, converse and drive (shown as Type into panes). | Pair the phone again. Scopes can be taken away from the device list in Settings → Remote and that takes effect immediately — but they cannot be added there. The checkbox for a scope a device does not hold is disabled on purpose. |
forbidden_scope, message this device may not take screenshots | Screen captures are a per-device flag, not a scope, because a capture shows your whole screen and not only DeckSpace. | Same — granted at pair time only. |
forbidden_permission | The device does hold the scope. A tool-permission category on the desktop is set to ask or deny, and a confirm arriving from a phone has no prompt anyone is standing at. | Set that category to allow on the desktop. The body names it in detail.category. |
pairing_closed | You typed a pairing code with no pairing window open, or with Remote switched off. | Open Settings → Remote on the desktop and start pairing there first. |
Only two categories can produce forbidden_permission, and which one depends on where the order was told to land:
| Order target | Category that must be allow |
|---|---|
| A board task (the default) | Board write |
| A headless mission | Shell commands — the mission runs an agent that writes files and runs commands in the repo |
The message the phone renders says to set the category in Settings → Permissions. The section is actually titled Tool permissions · pre-authorization. Same screen, and the category labels in it (Board write, Shell commands) match the message exactly.
The other refusals a phone can hit:
| Status | Code | Cause |
|---|---|---|
| 401 | pairing_invalid | The code is unknown, expired, or already used. |
| 409 | device_limit | Eight devices are already paired. Remove one first. |
| 409 | already_decided | The gate moved on between the list and your tap. Refetch. |
| 429 | — | 120 requests per minute per device. Five failed authentications lock that device out for 15 minutes. |
| 503 | renderer_unavailable | The DeckSpace window is closed on the desktop. |
A gate never reaches the phone
Symptom. Something is clearly waiting for a human on the desktop, and the phone’s approvals list is empty.
Three of the four gate kinds live only inside the DeckSpace window — the Maestro plan gate, the Maestro question wizard, and mission approvals. They are listed where they can be decided, so while the window is closed they are simply absent rather than shown and un-tappable. Reopen the window and the gate reappears with its original timestamp.
Every DeckSpace window is its own context. A mission started in a detached Loop panel keeps its approval handles in that window, and the main window never learns about it — so its gate never appears on the phone at all. Detaching the panel after the mission started in the main window is fine.
Fix. Start missions in the main window when you intend to approve them from your phone.
The desktop is also given 4 seconds to answer a phone’s request. An overrun is reported as stalled, not failed — approving a plan starts the whole run, so the decision is applied before the answer comes back. Do not tap twice.
Remote will not start
When the server refuses to come up, Settings → Remote gives a reason. Each maps to a specific fix.
| Reason | Fix |
|---|---|
disabled | Remote is off. Nothing is listening. |
not_pro | Remote is part of a subscription. Activate or renew under Plan & license, then turn it on. |
no_tailnet_address | Private mesh is selected but no address in 100.64.0.0/10 exists. Join the mesh, or switch to Local network. Private mesh never falls back to the LAN on its own. |
no_lan_address | Local network is selected but there is no 10.x, 172.16–31.x or 192.168.x address. Join a network and turn Remote on again. |
bind_failed | Another program holds the port. Quit it and turn Remote on again. |
listener_stopped | The server stopped on its own. Toggle Remote off and on; the log has the detail. |
no_server_id | The identity your phones use to recognise this machine could not be saved — usually an unwritable app database. Remote refuses to start rather than hand out an identity that changes next launch. |
If the mode is This machine only, the server answers on 127.0.0.1 and a phone cannot connect to it directly at all. That is the mode working, not failing. See Phone remote.
Linux-only failures
| Symptom | Why | Fix |
|---|---|---|
| The tray icon does not exist, and mini-mode hides the app with no way back | The tray needs a StatusNotifierWatcher on the session bus. GNOME removed its tray years ago. | Install the AppIndicator and KStatusNotifierItem Support extension. DeckSpace warns once at startup when it detects no host — and stays quiet when it cannot tell, so no warning is not proof of a tray. |
| The global hotkey and dictation insert are greyed out | Wayland blocks synthetic input and global shortcuts. An X11 session is required. | Use an X11 session, or use push-button voice in the Voice room — that still works, and the transcript goes to the clipboard for you to paste. |
| Voice replies are silent | Neither paplay nor aplay is installed. | Install pulseaudio-utils or alsa-utils. The app says this once, then stops repeating it. |
| Agents write outside the workspace | bwrap is missing, so there is no sandbox backend. | Install bubblewrap. See above — the toggle will read On either way. |
Activation and licence
DeckSpace requires a valid licence key and a device-bound activation token for that specific Mac. Both are checked offline at every launch.
| Message | Cause | Fix |
|---|---|---|
| “This license is already active on 2 Macs” | The seat cap is 2. | Remove the licence on one of those Macs, or use the device page on deckspace.dev, then activate again. |
| Renew prompt / subscription lapsed | The Stripe subscription behind the key has ended. Renewal is enforced through the activation token, not the key. | Renew, then relaunch so the app re-activates. |
| “That license key isn’t valid.” | The key was rejected outright. | Check it pasted whole — the keys are long and wrap in email. |
| “Couldn’t reach the license server.” | No network, or the store is unreachable. | Reconnect and try again. |
| Paid features locked after a long stretch offline | The activation token expires. The app tries to renew it silently once it is within 10 days of expiry — but that background attempt runs at most once per app launch, so a failed attempt is not retried until you relaunch. | Get online and relaunch DeckSpace. One successful refresh restores it. |
Smaller things
| Symptom | Cause and fix |
|---|---|
| “Can’t verify the developer” on first launch | Right-click DeckSpace → Open → Open. Once only. |
| A room shows an error panel instead of loading | Its code chunk failed or took more than 10 seconds. The app already retried once automatically. Press Retry; relaunching also re-fetches it. |
| “tmux not found” when opening a persistent session | Persistent sessions are tmux-backed so they survive quitting the app. Install tmux (brew install tmux). Ordinary panes do not need it. |
| An agent authenticates in one pane and not another | Panes where the CLI is the direct child have no shell in between to source your dotfiles. See the API-key note under agent CLI not detected. |
Still stuck
Send the last few dozen lines of the log file from the top of this page to info@deckspace.dev, with your platform and the app version from the chip beside the DeckSpace wordmark in the top-left of the window. A real person answers.