cd /news/ai-tools/show-hn-persistent-claude-code-sessiโ€ฆ ยท home โ€บ topics โ€บ ai-tools โ€บ article
[ARTICLE ยท art-55062] src=github.com โ†— pub= topic=ai-tools verified=true sentiment=ยท neutral

Show HN: Persistent Claude Code sessions you drive as a terminal or a chat

Agentpeek, a free self-hosted browser control panel for Claude Code, allows users to start persistent AI agent sessions on Linux/WSL2 that continue running even after closing a laptop, accessible from any browser over a tailnet. The tool uses tmux sessions and a FastAPI backend to manage session lifecycles, enabling users to drive AI agents for generating content like code, docs, and reports. It includes a companion viewer, filepeek, for rendering generated files.

read16 min views1 publishedJul 11, 2026
Show HN: Persistent Claude Code sessions you drive as a terminal or a chat
Image: source

agentpeek is the free, self-hosted browser control panel for Claude Code. Start a session on your Linux/WSL2 box, drive it as a terminal or a chat, and reach it from any browser over your tailnet โ€” it keeps working when you close the laptop.

You start Claude Code (or any shell) in a session, close the laptop, and it keeps working. Open the same browser tab from another machine and you're right back in it โ€” the session never stopped. Sessions are plain named tmux

sessions, so the exact same ones are reachable over SSH with tmux attach

.

agentpeek is where the work happens: you drive the AI agents that generate your content โ€” docs, code, reports, sites. Its companion ** filepeek** is the downstream viewer that renders the

.md

/.html

/.xlsx

files those agents leave behind. See Companion tool.

Browser โ”€โ”€โ–บ uvicorn/FastAPI :8090 (127.0.0.1)
              โ”œโ”€โ”€ /            static frontend (sidebar + iframe)
              โ”œโ”€โ”€ /api/...     session lifecycle REST API โ†’ tmux (app/multiplexer.py)
              โ””โ”€โ”€ /term/...    reverse proxy (HTTP + WebSocket) โ†’ ttyd :7681
                                 ttyd runs bin/agentpeek-attach <name>
                                   = replay history + tmux attach

Everything binds to 127.0.0.1

and is served from one port, so exposing it on the tailnet is a single command (see below). The terminal iframe is same-origin, which keeps clipboard access and tailscale serve

simple.

app/multiplexer.py

โ€” the only module that knows about tmux (swap point for another multiplexer).bin/agentpeek-attach

โ€” ttyd entrypoint; validates the session name, replays pane history (tmux capture-pane -S -

) so browser scrollback covers output from before the attach, thenexec tmux attach

.- Switching sessions in the sidebar swaps the iframe URL; the old iframe's WebSocket closes, ttyd reaps that tmux attach

client, and tmux detaches it cleanly โ€” the browser is never attached to two sessions.

On a fresh Debian/Ubuntu (incl. WSL2), setup.sh

installs everything it needs for you โ€” the OS packages (tmux

, curl

, python3-venv

) via sudo apt

, plus ttyd

, Claude Code, and the Python deps โ€” so it's one command. One thing it can't do for you:

systemd must be enabledโ€” the services run under it. On WSL2, add this to/etc/wsl.conf

, then runwsl --shutdown

in Windows (PowerShell) and reopen your terminal:Ubuntu 24.04 on recent WSL enables systemd by default;

[boot]
systemd=true

setup.sh

checks and tells you if it's missing.

Claude Code is installed via the official native installer (a standalone binary in ~/.local/bin

, no Node/npm). After setup, sign in from agentpeek's Claude chip (or run claude

once) โ€” needed for UI (chat) mode; shell mode needs no sign-in.

Also: you need git

to clone the repo and Python 3.10+ (already on Ubuntu 22.04/24.04); the bundled ttyd

binary is x86_64. For remote/mobile access you need Tailscale โ€” setup.sh

offers to install it for you (or set AGENTPEEK_INSTALL_TAILSCALE=1

/0

to skip the prompt).

โ€” the downstream viewer that renders thefilepeek(recommended companion).md

/.html

/.xlsx

/code files your agents generate, so you can browse them in the browser.setup.sh

offers to clone and install it next to agentpeek (or setAGENTPEEK_INSTALL_FILEPEEK=1

/0

to skip the prompt). SeeCompanion tool.

Clone into your Linux home, not a Windows drive โ€” a repo under /mnt/c/...

is slow and lacks real Unix permissions, so setup.sh

refuses to run there.

cd ~ && git clone https://github.com/thrinz/agentpeek.git && cd agentpeek
./setup.sh

It installs the apt prerequisites (tmux

/curl

/python3-venv

) and ttyd

if missing, installs Claude Code, creates .venv

and installs the Python deps (including claude-agent-sdk

), sources conf/agentpeek.tmux.conf

from ~/.tmux.conf

, installs and enables three systemd user services (agentpeek-tmux

, agentpeek-ttyd

, agentpeek

), and enables linger so everything starts at WSL2 boot. At the end it prompts you to set a browser login password (hashed into ~/.config/agentpeek/agentpeek.env

) and prints the URL โ€” http://localhost:8090

. It's idempotent โ€” safe to re-run โ€” and offers to install Tailscale if it isn't already present.

Custom ports. agentpeek uses 8090

(web) and 7681

(ttyd) by default. To run it alongside another service โ€” or another WSL distro that already holds those ports on a shared localhost

โ€” pass different ports at setup:

AGENTPEEK_PORT=9090 AGENTPEEK_TTYD_PORT=9091 ./setup.sh

setup bakes them into the systemd units (the app's reverse proxy follows the ttyd port automatically), so re-running with the same values is all it takes.

Headless / cloud (Linode, DigitalOcean, โ€ฆ). setup.sh

runs non-interactively when there's no TTY โ€” every prompt has an env override, so nothing blocks:

AGENTPEEK_INSTALL_TAILSCALE=1 ./setup.sh

For a one-shot deploy, paste deploy/cloud-init.sh into a Linode/DO droplet's

User Data field โ€” it clones, installs, joins your tailnet (with a Tailscale auth key), and serves agentpeek over HTTPS. See

for the StackScript and the Linode / DigitalOcean Marketplace submission steps.

deploy/MARKETPLACE.md

Passwordโ€” with no TTY and none set, setup** generates a strong temporary one**, prints it, and saves it to~/.config/agentpeek/initial-password.txt

(0600). It'sone-time: your first login is forced through achange passwordscreen, after which the temp password and that file are discarded. Override withAGENTPEEK_PASSWORD=โ€ฆ

(final, no forced change), orAGENTPEEK_NO_PASSWORD=1

to rely on tailnet ACLs only.Claude sign-inโ€” not done at setup (no host browser). After install, sign in from the** Claude chip**in the web UI, orclaude auth login

over SSH.Reach it over Tailscale onlyโ€” agentpeek binds127.0.0.1

; never expose8090

/7681

on the public interface.tailscale serve

is the intended door.- Cloud VMs are often rootโ€” setup setsIS_SANDBOX=1

so UI mode andcds

work (the agent then acts as root without per-tool prompts).

Expose on the tailnet (HTTPS, tailnet-only โ€” never the public internet):

tailscale serve --bg --https=9443 http://127.0.0.1:8090

The app is the same on macOS; only the installer differs (Homebrew + launchd instead of apt + systemd). Use setup-macos.sh

rather than setup.sh

:

git clone https://github.com/thrinz/agentpeek.git && cd agentpeek
./setup-macos.sh

It brew install

s tmux

and ttyd

, installs Claude Code, creates the venv, and renders the templates in launchd/ into

~/Library/LaunchAgents

(dev.agentpeek.tmux

, dev.agentpeek.ttyd

, dev.agentpeek.app

). The agents start at login (launchd RunAtLoad

, no linger needed). Same port overrides apply (AGENTPEEK_PORT=โ€ฆ AGENTPEEK_TTYD_PORT=โ€ฆ ./setup-macos.sh

). Manage it with:

launchctl kickstart -k gui/$(id -u)/dev.agentpeek.app   # restart
launchctl print     gui/$(id -u)/dev.agentpeek.app      # status
tail -f ~/Library/Logs/agentpeek/app.log                # live logs
launchctl bootout   gui/$(id -u)/dev.agentpeek.app      # stop

Tailscale on macOS installs from the App Store / standalone app, then tailscale serve

works exactly as above.

A prebuilt multi-arch image (linux/amd64

  • linux/arm64

) is published to GitHub Container Registry, so you can run agentpeek anywhere Docker runs โ€” Linux, macOS, or Windows (the container is Linux; Docker Desktop runs it on Mac/Windows). This is also the only way to run it on Windows, which can't host it natively.

docker pull ghcr.io/thrinz/agentpeek:latest

The quickest start is docker-compose.yml:


PROJECTS_DIR=~/projects docker compose up -d

PROJECTS_DIR=~/projects ANTHROPIC_API_KEY=sk-ant-... docker compose up -d

It mounts your projects

tree (the agent's working files), and named volumes for the app state (~/.config/agentpeek

) and Claude auth (~/.claude

) so they survive restarts. UI (chat) mode needs Claude signed in, but the API key is optional โ€” leave it out and sign in with your Pro/Max subscription from the in-app Claude chip (it persists in the ~/.claude

volume), mount your host ~/.claude

to reuse an existing login, or pass ANTHROPIC_API_KEY

for per-token billing.

Exposure:the compose file bindsโ€” agentpeek runs an autonomous agent, so don't publish it on the open internet. Reach it over your tailnet:127.0.0.1

on purposetailscale serve --bg --https=9443 http://127.0.0.1:8090

.

The image is built and pushed by .github/workflows/docker.yml on every push to

main

and every v*

tag (no cloud credentials โ€” it uses the built-in GITHUB_TOKEN

).The sidebar groups sessions into collapsible folders you define yourself (create them with the folder button; up to two nested levels). A General folder always exists as the catch-all โ€” sessions created from the CLI land there. The folder is stored on the tmux session itself as the @agentpeek_group

session option, so it survives renames; the folder list lives in ~/.config/agentpeek/folders.json

.

The create dialog asks for: a folder (required), a Type โ€” Shell (a tmux terminal) or UI (a Claude chat โ€” see UI mode) โ€” a working directory picked from a collapsible tree rooted at ~/projects

(DIRS_ROOT

in app/main.py

), and for Shell type a start option (AI types your launcher command into the new shell, or plain Shell).

AI start optiontypescds

into the new shell.setup.sh

installscds

to~/.local/bin

asclaude --dangerously-skip-permissions

(edit that file, or thesend-keys

command inapp/multiplexer.py

, to launch a different agent likeaider

). It won't overwrite an existingcds

, so your customisation sticks.

Running as root?Claude Code refuses to skip permissions as root, so UI mode andcds

fail with exit 1.setup.sh

detects root and setsIS_SANDBOX=1

in the env file (andcds

sets it too) to allow it โ€” this lets the agent act as root without per-tool prompts, so prefer running agentpeek as a normal user.

For an AI (Shell + AI) session the create dialog also offers โ€œNotify my phone when Claude finishesโ€ with a dropdown of remembered ntfy topics โ€” see below.

Get a push on your phone every time Claude finishes a turn in a session, using the free ntfy.sh service. Three pieces cooperate:

A Claude Code Stop hook(one-time, per machine). Add this to~/.claude/settings.json

โ€” it pushes only whenCLAUDE_NOTIFY_TOPIC

is set, so sessions without a topic stay silent:

{
  "hooks": {
    "Stop": [
      { "hooks": [
        { "type": "command",
          "command": "[ -n \"$CLAUDE_NOTIFY_TOPIC\" ] && curl -s -H \"Title: Claude Code\" -d \"Claude finished a turn in $(basename \"${CLAUDE_PROJECT_DIR:-session}\")\" \"https://ntfy.sh/$CLAUDE_NOTIFY_TOPIC\" >/dev/null 2>&1 || true",
          "async": true }
      ] }
    ]
  }
}

setscds

CLAUDE_NOTIFY_TOPIC

from its first argument โ€”cds

(off),cds on

(uses$CDS_NOTIFY_TOPIC

, export your default in your shell rc), orcds <topic>

(a custom topic). The installedbin/cds

already does this. - The create dialog ties it together: tickNotify my phone, pick or add a topic, and agentpeek typescds <topic>

into the new session for you. Topics you add are remembered in the browser for next time.

On your phone, install the ntfy app (iOS/Android) and subscribe to the same topic name. Topics are public unguessable strings โ€” anyone who knows the name can read its messages, so use something random like claude-$(openssl rand -hex 4)

and don't share it.

On phones/tablets a touch key bar appears under the terminal when a Shell session is attached, with the keys a soft keyboard can't reliably send to a terminal: esc, tab, ctrl (sticky โ€” tap it then a letter for Ctrl-combos), arrow keys, scroll up/down, and paste. Every button routes through tmux send-keys

on the server (POST /api/sessions/<name>/keys

), so it works even where iOS Safari's keyboard events don't reach xterm. Pair it with tailscale serve --https

so the clipboard-based paste has a secure context.

A UI session is a chat with the Claude Agent SDK running in the chosen directory โ€” the same agent as a terminal Claude Code session (it edits files, runs bash, uses tools), rendered as a chat instead of a terminal. It needs the claude

CLI installed and a Claude connection.

Streaming token-by-token with a live cursor;markdown + GFM tables + code rendering with copy-code buttons; tool calls and results shown inline (collapsible).Message queueโ€” keep typing while the agent works; messages run in order.** Stopbutton or Esc**interrupts the current turn.in the input autocompletes files in the working directory to reference.@

Model picker(Opus / Sonnet / Haiku) per session, switchable live; per-turn and running** costshown in the header; optional voicein (mic โ†’ speech-to-text) and out (speak replies). Always-on background agents**โ€” the agent keeps running if you close the browser; reconnecting (auto-reconnect on drop) reloads the transcript and the conversation resumes (SDKresume

). Idle agents (no browser, not working) are suspended after 30 min and resume on the next message. Sessions run autonomously (bypassPermissions

) โ€” same trust model as your terminal.

Registry + transcripts live under ~/.config/agentpeek/

(ui_sessions.json

, ui_transcripts/

).

UI-mode sessions run the Claude Agent SDK, which authenticates with whatever the host's claude

CLI uses. The Claude chip at the bottom of the sidebar shows the connection state and, when not connected, opens a sign-in panel:

Sign in with Claude drivesclaude setup-token

server-side: it prints an authorize URL (shown in the panel), you approve it in your browser and paste the code back, and the resulting long-lived subscription token is saved asCLAUDE_CODE_OAUTH_TOKEN

in~/.config/agentpeek/agentpeek.env

โ€” the same flow as/login

in the terminal. (Requires a Claude Pro/Max/Team/Enterprise plan.)Use an API key saves anANTHROPIC_API_KEY

instead (bills per-token).

If the host is already logged into Claude Code (~/.claude/.credentials.json

exists), the chip shows connected and nothing else is needed. Disconnecting clears only agentpeek's saved token/key โ€” it never touches the host's own login.

UI mode runs Claude Code, which can route to Amazon Bedrock or Google Vertex AI instead of the first-party Anthropic API. agentpeek detects this and adapts: the chip shows "Using AWS Bedrock / Google Vertex", the in-app sign-in panel is hidden (the cloud provider authenticates you), the per-turn cost is hidden (billing goes through the cloud account), and the model picker is hidden โ€” on these backends the opus

/sonnet

/haiku

aliases don't resolve, so agentpeek lets Claude Code use the model you set in the environment.

There's no UI for this yet โ€” configure it by hand in ~/.config/agentpeek/agentpeek.env

(loaded by the systemd unit; for the manual uvicorn

run, the vars must be in the process environment). Set the backend flag, the model IDs, and the provider credentials, then restart agentpeek.

Amazon Bedrock:

CLAUDE_CODE_USE_BEDROCK=1
AWS_REGION=us-east-1
AWS_PROFILE=default
ANTHROPIC_MODEL=us.anthropic.claude-opus-4-...-v1:0
ANTHROPIC_SMALL_FAST_MODEL=us.anthropic.claude-haiku-4-...-v1:0

Google Vertex AI:

CLAUDE_CODE_USE_VERTEX=1
CLOUD_ML_REGION=us-east5
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project
ANTHROPIC_MODEL=claude-opus-4-...
ANTHROPIC_SMALL_FAST_MODEL=claude-haiku-4-...

Look up the exact model IDs available in your account/region from the provider console (they change over time). The agent's tools (bash, file edits, etc.) run locally on your box regardless of which backend serves the model. Note that Anthropic's server-hosted features (Managed Agents, server-side tools) aren't available on Bedrock/Vertex โ€” but agentpeek uses the local Claude Code agent, so that doesn't affect it.

agentpeek binds to 127.0.0.1

only and is reachable remotely solely through Tailscale (tailscale serve

), so the baseline access control is tailnet membership/ACLs. Do not port-forward 8090/7681 to any public interface.

Optional key-based login (same scheme as filepeek) adds a second factor on top of the tailnet. The easiest way to enable it is to run ./setup.sh โ€” it prompts for a password, hashes it, writes

~/.config/agentpeek/agentpeek.env

, and restarts the service. (Re-run it any time to change the password, or set AGENTPEEK_PASSWORD=โ€ฆ ./setup.sh

to do it non-interactively.)To wire it up by hand instead, set either env var in ~/.config/agentpeek/agentpeek.env

(read by the systemd unit):

.venv/bin/python -m app hash-password
AGENTPEEK_PASSWORD_HASH=pbkdf2_sha256$200000$....   # browser login
AGENTPEEK_TOKEN=<random-string>                      # scripted access: Authorization: Bearer <token>
AGENTPEEK_SECRET=<random-string>                     # keeps sessions valid across restarts

With auth on, the browser logs in at /login

(PBKDF2-checked password โ†’ signed, HttpOnly session cookie, 7-day TTL, per-IP lockout after repeated failures) and the terminal WebSocket is gated on the same cookie. With neither var set, the app is open โ€” the accepted posture for a purely local, single-operator setup. Turning it on is recommended once UI mode can drive agents that edit files over the tailnet.

Each session's โ‹ฎ menu has Copy SSH attach command, which copies a ready one-liner for that exact session:

ssh -t <user>@<tailscale-hostname> tmux attach -t <session-name>

(-t

forces a TTY so tmux runs interactively.) There is also Copy local attach command (tmux attach -t <name>

) for when you are already on the host.

history-limit 1000000

โ€” "unlimited" scrollback, bounded by memory (applies to sessions created after the setting is loaded).terminal-overrides ',xterm*:smcup@:rmcup@'

+status off

โ€” disables the alternate screen and the tmux status bar so output flows into the browser's native scrollback buffer and wheel-scrolling/select-to-copy work without tmux copy-mode. Side effects: SSH attaches also get no status bar, and the screen isn't restored after detaching in a CLI terminal. Both accepted for this tool.mouse off

โ€” xterm.js owns selection (select โ†’ Ctrl/Cmd+C copies natively).

  • The tmux server runs on a dedicated socket(tmux -L agentpeek

) owned byagentpeek-tmux.service

, in its own cgroup. So restarting or crashing the app (agentpeek

/agentpeek-ttyd

)never touches your sessionsโ€” selected viaAGENTPEEK_TMUX_SOCKET=agentpeek

(honored byapp/multiplexer.py

andbin/agentpeek-attach

; same socket in the launchd and Docker setups). Stopping It usesagentpeek-tmux

is the only thing that kills the tmux server and all sessions.exit-empty off

, so it also stays up with zero sessions (no placeholder session needed). To reach it by hand:tmux -L agentpeek ls

.Terminal sessions are restored after the tmux server dies(a host reboot, or a Docker container restart โ€” where the server lives inside the app container). agentpeek keeps a manifest at~/.config/agentpeek/shell_sessions.json

(name, dir, group, and for AI sessions a Claude session id) and recreates the sessions on startup. AI sessions arelazily resumed: each is recreated empty with a pendingcds --resume <id>

thatbin/agentpeek-attach

runs the first time you open it, so a restart never spawns every Claude at once. Resume needs Claude's transcripts (~/.claude

) โ€” already a persisted volume in Docker.- Logs: journalctl --user -u agentpeek -u agentpeek-ttyd -f

. - The sidebar polls every 3 s and on tab focus, so CLI-created sessions appear automatically.

agentpeek is the primary tool โ€” it's where AI agents generate your content. ** filepeek** is its downstream companion; together they cover the loop of working with AI agents on a remote/WSL2 box:

agentpeekโ€”runthe agents that generate the content: persistent browser terminal sessions.filepeekโ€”viewwhat they produced: renders the Markdown, HTML, Office, and code files those agents generate, in the browser.

Both are self-hosted, single-operator tools that bind to 127.0.0.1

and go over Tailscale, and they share a design language.

Install filepeek. setup.sh

offers to clone and install it next to agentpeek (answer the Install filepeek now? prompt, or set AGENTPEEK_INSTALL_FILEPEEK=1

to do it non-interactively). To do it by hand instead:

cd ~ && git clone https://github.com/thrinz/filepeek && cd filepeek
./install.sh
FILEPEEK_ROOT=~/projects .venv/bin/python app.py
tailscale serve --bg --https=9444 http://127.0.0.1:8765
โ”€โ”€ more in #ai-tools 4 stories ยท sorted by recency
โ”€โ”€ more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain โ€” perfect for shipping the agent you just read about.

$git push zahid main
โ†’ Live at https://your-agent.zahid.host โœ“
Get free account โ†’ Pricing
from โ‚ฌ0/mo ยท no card required
LIVE [news/show-hn-persistent-cโ€ฆ] indexed:0 read:16min 2026-07-11 ยท โ€”