Skip to content

feat!: make pier great again - #40

Merged
kerem-kaynak merged 24 commits into
mainfrom
feat/simpler-faster-pier
Sep 28, 2026
Merged

kerem-kaynak merged 24 commits into
mainfrom
feat/simpler-faster-pier

Conversation

@kerem-kaynak

@kerem-kaynak kerem-kaynak commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

A session's startup was dominated by the repo's own setup, which the toolchain-only image couldn't cache, and every session repeated it. Parking discarded everything that was running, so waking a session meant rebuilding the tmux layout and restarting services by hand. And a headless session couldn't finish a browser login.

Startup

  • pier bake prebuilds the repo: it runs .pier/setup.sh on a checkout, scrubs every file pier pushed into the instance (credentials, env files, logs, containers), and images the result. Sessions from the image fetch only what changed and run setup on warm caches.
  • Setup runs once per session. Claiming a pooled session doesn't re-run it.
  • The pool's generation includes the in-VM supervisor, so pooled sessions filled by an older pier are replaced rather than claimed.
  • AWS launches from a baked image request provisioned-rate volume initialization, so first reads don't stall on blocks lazily loaded from S3 (with a fallback for CLIs that predate the field). Bakes wait out large snapshots instead of the CLI waiter's fixed ten minutes.
  • Independent pre-launch lookups run concurrently, and progress lines carry elapsed time.

Parking

  • The supervisor snapshots the tmux layout (windows, panes, folders, scrollback) and the running Docker containers, and restores both at boot. Claude and Codex resume their conversations; other commands come back typed at the prompt, not run.
  • The bootstrap restarts the supervisor after installing it, so a baked image's older binary can't keep running a session's first boot.
  • sshd drops dead connections after about a minute. Before, a dropped attach left a phantom tmux client that read as attached and kept the session from parking.
  • New windows and splits keep the folder they were opened in.

Connecting

  • While attached, the session uses your browser. URLs it opens (through xdg-open or $BROWSER) open on your laptop, and ports it listens on are reachable at the same localhost port. So any in-session browser login works — an MCP server's OAuth, a CLI's web login — for every agent.
  • The public-IP lookup that gates direct ssh on AWS races several services with a short cap, so a filtered or slow one no longer makes every command wait out its timeout and fall back to the tunnel.
  • The list call returns each instance's address, so status reads skip a describe call per session. The app shows the list first and fills in statuses after.
  • Attaching stays in the app until the VM answers, then shows a connecting screen through the handover instead of the user's own shell. A session whose ssh key isn't on this machine fails at once with a clear error.

Using it

  • One session-image concept, with one setting for whether the repo is included.
  • Speed profiles (Fast, Lean, Minimal) preset the pool size and bake reminders.
  • Reminders only state facts (no image, image age, .pier scripts changed since the bake); pier never rebakes on its own.
  • pier setup shows every default with its cost for confirmation and offers to bake the current repo.
  • Settings is one page grouped by what you're changing, showing only the active cloud, with an accent color under Appearance.
  • The app has Sessions, Repos and Settings tabs and a detail pane.
  • Rows show one steady state (starting, running, parked). The supervisor's activity reading lives in the detail pane, and a claimed session reads starting until it's usable.
  • The pool is managed from the Repos tab (+/- size, f fill, x drain) or with pier pool, pier pool set <n>, pier pool fill and pier pool drain [repo].

Structure

  • pkg/pier holds every capability behind one client used by the CLI and the app, and later a Mac app. It never prints, exits or reads stdin, and reports progress as typed events.
  • Drivers no longer write to the terminal; frontends route transfer meters and notices.

Breaking changes

  • pier mcp login is removed; log in from inside the session.
  • pier skills → pier setup --skills.
  • pier pool's subcommands are set, fill and drain.
  • pier ls shows the steady states (pier ls --json keeps the detailed ones).

Bake now checks the repo out on the bake instance, runs .pier/setup.sh to
completion, scrubs everything the create cargo delivered, and images the
result. The bootstrap reuses a checkout it finds, so sessions from the
image fetch only the delta and re-run setup warm. AWS launches from baked
images request provisioned-rate volume initialization, falling back when
the CLI predates the field. Create, claim and fill progress lines carry
elapsed time.
pkg/pier is now the one API the CLI, the TUI and the coming Mac app use.
It owns every capability behind a Client, reports long operations as
typed events, returns typed errors, and never prints, exits or reads
stdin. Drivers no longer write to the terminal: transfer meters, streamed
bake output and transport notices go through fields the frontend sets.

Session images are one concept. `pier bake` prebuilds the repo by default
and records what it baked from, so pier can remind — never decide — when
a rebake would help. Speed profiles (fast, lean, minimal) preset how many
ready sessions a baked repo keeps and whether reminders show.

Setup shows every default for confirmation and offers to bake the repo it
runs in. Settings is one page grouped by cloud, new sessions, idle and
cost, and speed, showing only the active cloud. The app has Sessions,
Repos and Settings tabs, titled panels, a detail pane and a live cost
header.

BREAKING CHANGE: `pier pool` is replaced by `pier ready [n]` and
`pier repos`, `pier skills` by `pier setup --skills`, and `--no-pool` by
`--no-ready`. Baked repos keep one ready session by default.
Parking is a shutdown, so a wake used to land in a fresh tmux server with
an empty shell. The supervisor now snapshots the agent user's tmux every
~30s and right before it parks (~/.pier/tmux: state.json plus per-pane
scrollback), and a new pier-restore.service runs `pier-supervisor restore`
once per boot to rebuild it: sessions, windows, layouts, cwds, replayed
scrollback, claude relaunched with --resume <id> or --continue, codex with
`codex resume --last`, other commands typed at the prompt but not run, and
setup windows as a plain shell. Attach waits briefly for its marker. Pool
claims and the prebuild scrub drop the saved layout.

Deviations from the design:
- Agents start from a one-shot PROMPT_COMMAND in the restored shell, not by
  exec'ing the agent from the pane command. Startup files run before bash
  enables job control, so a program started there shares the shell's
  process group: tmux reports "bash" as the pane command and Ctrl-Z can't
  reach the agent. Quitting the agent also leaves a shell, not a closed pane.
- The restored shell reads a generated rcfile instead of being a login shell
  (it sources /etc/profile and ~/.profile itself). pier's ~/.bashrc cd's
  into the repo, which would override the pane's saved cwd.
- Typed-back commands wait until the pane's tty leaves canonical mode
  (readline is reading). Keys sent earlier are echoed by the tty and then
  again by readline.
- The list-panes separator is printable ("|pier|"): tmux 3.2 (Ubuntu
  22.04) rewrites tabs in format output as '_'.
- Freshen waits (bounded) for the restore marker before killing the server,
  so a restore still in flight can't start a server after the kill.
- Both units set RuntimeDirectoryPreserve=yes so a supervisor restart does
  not delete the /run/pier/restored marker they share.
- A few claude flags (--dangerously-skip-permissions, --model,
  --permission-mode) carry over to the relaunch, so an unattended agent
  isn't left waiting on permission prompts. A positional prompt never
  carries over.
… resume

A pane whose root process is the program itself (tmux ran claude directly)
was saved as the first child in its process group — one of claude's MCP
servers — so a wake typed that command back instead of relaunching claude.
Children are only searched when the pane's root is a shell.

A claude or codex pane with no conversation to resume made --continue exit
with an error and left a bare shell; the relaunch now falls back to a fresh
agent.
setup.sh builds an environment; start.sh brings a built one up, the way a
laptop runs `docker compose up` after a reboot rather than a fresh install.
Sessions from a prebuilt image and ready-session claims now run start.sh
when the repo has one, instead of re-running the whole setup, and a parked
session's boot runs it after the tmux restore, so it wakes with its
services running. All three go through one session-up script the bootstrap
writes (modes setup, warm, wake); parked ready sessions skip the wake start
because their claim runs it.

Restore no longer brings back pier's own setup and start windows: their
run is over, `pier logs` keeps the output, and a wake starts a fresh one.

The shell rc only moves a login into the repo when it starts in $HOME, so
new tmux windows and splits keep the folder they were opened in; older
images get the line rewritten at first boot.
Setup runs when a session is built and never again. A ready-session claim
resumes, checks out the branch and refreshes secrets without re-running
it, and a wake re-runs nothing. What a wake needs is what was running: the
supervisor records the running Docker containers with each tmux snapshot
and before it parks, and the first supervisor of each boot starts exactly
those again once the daemon answers — any repo, no extra file.

BREAKING CHANGE: `.pier/start.sh` is no longer read.
The pool keeps its name. `pier pool` shows it, `pier pool set <n>` sizes it,
`pier pool fill` tops it up now and `pier pool drain [repo]` empties any
repo's pool from anywhere; fills still also happen by themselves after
every create and size change. The app's Repos tab has the same actions:
+/- sets the size, f fills, x drains.

The speed setting is speed.pool_size; configs that stored it under its
interim name are read as before.

BREAKING CHANGE: `pier ready` is replaced by `pier pool`, and `--no-ready`
by `--no-pool`.
A row shows one word per session — starting, running, parked, deleting or
create failed. The supervisor's activity reading (attached, working, idle)
changes every few seconds, so it moved to the app's detail pane, and
"attached" appears only when the supervisor actually reports a terminal;
an unreadable status no longer reads as attached. `pier ls --json` keeps
its raw states.

A claim used to list its session as parked while resuming the stopped
pool member. The claim now clears the ready marker at its commit point and
MarkReady sets it once the session is usable, so it reads starting in
between, like a fresh create; a claim that died midway reads parked again
after 15 minutes. Background refreshes no longer flash the spinner.
…ches

The public-IP lookup that gates direct ssh depended on one service; where a
network filters it (secure gateways and VPNs do), every command waited out
its timeout and fell back to the tunnel. Three services are now raced with
a two-second cap. The list call also returns each instance's address and
security group, so the probes that follow it skip their own describe call,
and the app shows the list as soon as it lands and fills in each session's
status after.

Attaching from the app stays on screen ("connecting…") until the VM
answers, then hands over the terminal; failures show in the app. A session
whose ssh key isn't on this machine fails at once with a clear error
instead of ssh retrying for minutes.

sshd never timed out a dead connection, so a dropped attach left a phantom
tmux client: the session read as attached and could never park. Sessions
now drop dead connections after about a minute.

The new-session input is a plain text field.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

One setting in a new Appearance group: teal (the default, the terminal's
own cyan), navy, violet, emerald, orange, crimson, pink, amber or graphite.
It recolors the app's borders, tabs, keys and highlights at once and the
CLI's accents too. Dark accents draw the active tab's text in white.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Attaching hands the terminal to ssh, and until the session drew itself
the user's own shell showed through — it read as if pier had quit. The
handover now opens a blank screen reading "connecting to <session>…" that
tmux draws over. The CLI restores its screen after the detach; the app
redraws itself. The attach's exit status is unchanged.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

A headless session can't finish a browser login: the login page has
nowhere to open, and the provider's redirect to localhost reaches the
laptop, where nothing listens. While someone is attached, both now work.
The session's xdg-open and $BROWSER hand URLs to the supervisor, which
sends them over a socket the attach forwards, and they open in the
laptop's browser (http and https only). A side connection mirrors the
ports the session listens on to the same localhost ports, so callbacks
and dev servers are reachable. Any agent or CLI's browser login — an
MCP server's OAuth, `gh auth login` — works from inside the session.

The pool's generation now includes the in-VM supervisor: a claim doesn't
reinstall it, so a pooled session filled by an older pier would carry the
older VM-side behavior into the session.

BREAKING CHANGE: `pier mcp login` is removed; log in from inside the
session instead.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@kerem-kaynak kerem-kaynak changed the title feat!: prebuilt session images, restore on wake, a shared API and a redesigned app feat!: make pier great again Sep 28, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@kerem-kaynak
kerem-kaynak merged commit 1b9548d into main Sep 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant