You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using Plannotator via a remote Claude Code instance (SSH, cloud VM, headless server), the hook starts a local server and either:
Tries to open a browser that doesn't exist, or
Correctly detects the remote session (SSH_TTY/SSH_CONNECTION/PLANNOTATOR_REMOTE=1) and skips the browser — but then there's no way to actually view the plan review UI from your local machine.
The server binds to localhost:<port> on the remote host, which isn't accessible from the user's local browser. The user is stuck — they can see the URL printed in the terminal, but can't open it.
This is a real blocker for anyone using Claude Code over SSH (which is a very common setup — remote dev servers, cloud instances, codespaces, etc.).
Expected Behavior
When running on a remote instance, Plannotator should give the user a shareable URL they can open in their local browser — no tunneling, no port forwarding required.
Suggested Solutions
Option A: Auto-generate share URL in remote mode (Recommended)
When a remote session is detected, automatically generate the compressed share URL (or short paste URL from #188) and print it to the terminal. The user copies the link, opens it on their local machine via share.plannotator.ai, done.
🔗 Remote session detected — open this link on your local machine:
https://share.plannotator.ai/p/aBcDeFgH
(or full URL: https://share.plannotator.ai/#eJy0Vk1v4z...)
Waiting for your decision...
This is the cleanest UX — zero setup, works everywhere. The share portal is already a static SPA that works independently of the server.
Option B: --url-only / --no-open CLI flag
Add a flag that skips the browser open and just prints the share URL:
# In .claude/hooks/ExitPlanMode.sh
plannotator --url-only
This gives users explicit control regardless of remote detection.
Option C: Clipboard-friendly output
When remote, copy the share URL to the remote clipboard (if available via xclip/xsel/OSC 52 terminal escape) and print it. Many modern terminals (iTerm2, WezTerm, kitty) support OSC 52 for remote-to-local clipboard sync.
Option D: QR code in terminal
Print a QR code in the terminal that the user can scan with their phone to open the review. Libraries like qrcode-terminal make this trivial. Great for quick mobile review.
Implementation Notes
Remote detection already works well in packages/server/remote.ts (PLANNOTATOR_REMOTE, SSH_TTY, SSH_CONNECTION)
The onReady callback in all three modes (plan/review/annotate) already receives isRemote — just needs to generate and print a share URL when isRemote === true
The handleServerReady/handleReviewServerReady/handleAnnotateServerReady functions are the exact place to add this logic
Should work for all three modes: plan review, code review (plannotator review), and annotate (plannotator annotate)
Workaround (current)
Users can manually set up SSH port forwarding:
ssh -L 19432:localhost:19432 remote-host
But this is fragile (port changes), requires planning ahead, and is a terrible UX.
I use Claude Code on remote instances daily and this is the #1 thing preventing me from using Plannotator in that workflow. Happy to create a PR implementing any of these approaches — Option A seems like the best default behavior.
Problem
When using Plannotator via a remote Claude Code instance (SSH, cloud VM, headless server), the hook starts a local server and either:
SSH_TTY/SSH_CONNECTION/PLANNOTATOR_REMOTE=1) and skips the browser — but then there's no way to actually view the plan review UI from your local machine.The server binds to
localhost:<port>on the remote host, which isn't accessible from the user's local browser. The user is stuck — they can see the URL printed in the terminal, but can't open it.This is a real blocker for anyone using Claude Code over SSH (which is a very common setup — remote dev servers, cloud instances, codespaces, etc.).
Expected Behavior
When running on a remote instance, Plannotator should give the user a shareable URL they can open in their local browser — no tunneling, no port forwarding required.
Suggested Solutions
Option A: Auto-generate share URL in remote mode (Recommended)
When a remote session is detected, automatically generate the compressed share URL (or short paste URL from #188) and print it to the terminal. The user copies the link, opens it on their local machine via
share.plannotator.ai, done.This is the cleanest UX — zero setup, works everywhere. The share portal is already a static SPA that works independently of the server.
Option B:
--url-only/--no-openCLI flagAdd a flag that skips the browser open and just prints the share URL:
# In .claude/hooks/ExitPlanMode.sh plannotator --url-onlyThis gives users explicit control regardless of remote detection.
Option C: Clipboard-friendly output
When remote, copy the share URL to the remote clipboard (if available via
xclip/xsel/OSC 52 terminal escape) and print it. Many modern terminals (iTerm2, WezTerm, kitty) support OSC 52 for remote-to-local clipboard sync.Option D: QR code in terminal
Print a QR code in the terminal that the user can scan with their phone to open the review. Libraries like
qrcode-terminalmake this trivial. Great for quick mobile review.Implementation Notes
packages/server/remote.ts(PLANNOTATOR_REMOTE,SSH_TTY,SSH_CONNECTION)onReadycallback in all three modes (plan/review/annotate) already receivesisRemote— just needs to generate and print a share URL whenisRemote === truehandleServerReady/handleReviewServerReady/handleAnnotateServerReadyfunctions are the exact place to add this logicplannotator review), and annotate (plannotator annotate)Workaround (current)
Users can manually set up SSH port forwarding:
But this is fragile (port changes), requires planning ahead, and is a terrible UX.
I use Claude Code on remote instances daily and this is the #1 thing preventing me from using Plannotator in that workflow. Happy to create a PR implementing any of these approaches — Option A seems like the best default behavior.