Skip to content
handup
  • How it works
  • Pricing
  • Blog
  • Docs
  • GitHub
Download
handup · betaDownloads · Agents · Compare · Blog · Changelog · llms.txt · API · Docs · GitHub · Support · Terms · Privacy

Changelog

What changed in handup

Every release, newest first. Unreleased changes are in development and not yet in a published download.

Unreleased

In development

Added

  • handup remote tailscale enables remote access after checking Tailscale, applies the mode with a daemon restart when needed, and prints the URL and pairing hint; handup never changes your Tailscale configuration. handup remote off turns the listener off without unpairing devices. With remote access off, handup pair on a terminal offers to enable Tailscale access; JSON and non-interactive pairing give the command to run instead. handup doctor checks Tailscale and whether the remote port answers, and setup suggests enabling remote access when Tailscale is connected

Changed

  • omp turn-end notices (integrations.omp.turn_notice) are skipped when you are already looking at that omp: in kitty with remote control enabled (allow_remote_control and listen_on), a turn that ends while its tab is the focused tab of the focused kitty window posts no notice. Other terminals, ssh, tmux, or kitty without remote control still get every notice. Run handup integrate omp to upgrade the extension, then restart omp.

Fixed

  • Ctrl+Enter (⌘+Enter on macOS) in the feedback or reply box now sends it: Approve with your feedback, or OK on a notice, without leaving the box. Enter still adds a new line, and the box label shows the shortcut on desktop-width screens
  • With Tailscale remote access enabled and no remote.bind, the daemon starts locally even when Tailscale is disconnected at boot, login or resume, instead of exiting and leaving agents unable to ask. It retries every 5 seconds and binds the remote listener once Tailscale connects; pairing reports that remote access is waiting for Tailscale while local requests and decisions keep working

v0.1.7

2026-10-08

Added

  • Inbox search in the desktop app, Android app and web inbox: the filter row's search icon or / opens a field, hidden by default, matching a case-insensitive substring in title, summary, folder, repo, branch, agent or session title together with chip filters. Esc or × clears and closes it, reset clears both search and filters, and no matching requests shows No matches; / now searches Inbox as well as History
  • History API: outcome, kind and agent accept comma-separated lists, matching any value within a filter and combining different filters; invalid outcome or kind values return 400, and single-value queries work as before
  • Licensed Android push gateway is live at https://push.gethandup.dev for license holders using the official app: notifications.backends now defaults to [desktop, push]; an installed signed license and proof-of-possession device ticket are required, so only verified phones get woken, without customer Firebase credentials. Android handles verification pushes silently and registers the token and optional ticket with every paired daemon. notifications.push.url selects the gateway and notifications.push.payload defaults to wake (no title or previews); title opts into redacted titles. handup doctor reports local verified-phone prerequisites and warns no verified phone for tokens without tickets; readiness does not prove phone delivery. Relay pairings also register tokens with the daemon; self-hosted fcm takes precedence over push
  • Releases include the self-hosted relay for Linux servers: handup-relay_<version>_linux_amd64.tar.gz and _linux_arm64.tar.gz (static musl binary with LICENSE and THIRD_PARTY_NOTICES.md), listed in checksums.txt and releases.json (kind relay); the downloads page has a Self-hosted relay section and the relay guide shows how to install it
  • Android: computers paired through a relay now get push wake-ups when the relay has compatible sender credentials. The phone registers its push token with both the relay channel and the computer, so relay push: wake and push: title notifications reach it; they show the relay title or "Approval requested" and open the inbox, and unpairing attempts to remove both registrations. Direct and Tailscale pairings are unchanged; the official app only receives relay wake-ups when the relay's FCM service account belongs to the app's Firebase project
  • Dictation in the desktop app, Android app and web inbox: a mic button beside free-text answers, feedback and reply fields, text fields and email subject/body appends what you say; the text stays editable and nothing is sent until you submit, and read aloud stops before listening. The desktop app transcribes on your computer with whisper.cpp using a model you install in Settings → Dictation (tiny, base or small; default base; SHA-256 checked, works offline afterwards; macOS asks for microphone access); Android uses the phone's speech recognizer (on-device when supported, otherwise Google's speech service; Settings says which) and asks for microphone permission; the web inbox uses the browser's speech recognition where available
  • Optional cloud speech providers in the desktop and Android apps: dictation with OpenAI, Groq or ElevenLabs and read aloud with OpenAI or ElevenLabs. API keys entered in Settings are stored only in the OS keychain or Android Keystore, are never shown again and never go to the daemon or relay; the provider receives your audio or text and may charge for it
  • Desktop dictation on Linux reuses a whisper.cpp model another speech tool already downloaded, so there is nothing to install: voxtype (Omarchy's dictation), hyprwhspr, Handy, OpenWhispr, Speech Note, GNOME Speech2Text, whisper-dictation, Buzz and AUR whisper.cpp-model packages. The same size as the selected model is preferred, a model installed in handup always wins, and Settings → Dictation shows which one is used (for example "Using base.en from voxtype (English only)")
  • Vim keys in the Inbox and History: G (or End) jumps to the last request and g g (or Home) to the first; Ctrl+D / Ctrl+U scroll the request on screen half a screen, Ctrl+E / Ctrl+Y a line and Page Down / Page Up a screen. All but g g and Home can be remapped under keys: in the config (scroll_half_down, scroll_line_up, last, …)
  • handup doctor reports push delivery: which route sends notifications (self-hosted FCM or the licensed gateway), verified and unverified phones, whether the push gateway is reachable, and the last delivery result with its age (for example "refused 5m ago: rate limited"). It also checks that a configured relay answers. Network checks time out after 3 seconds and only warn
  • Flood control for a runaway hook or agent session (one agent + session): after notifications.burst.max (20) notifications per notifications.burst.window (1m) the rest are skipped and one summary ("N more from <agent>") follows; a source sending requests.flood_threshold (40) requests in a minute shows an inbox banner with Mute 1h, Dismiss all (N pending) and hide, and is listed by handup flood with ready-to-run mute/clear commands and a rules snippet; handup flood clear --agent A [--session S] dismisses its pending notices and denies its other pending requests ("Cleared as a flood"); handup mute --agent/--session [--for 1h] [--dismiss-notices] silences a source until removed or a daemon restart (handup unmute ID|--all); past requests.max_per_minute (120) a source's new requests are refused with HTTP 429 code rate_limited. Each limit takes 0 to turn it off. API: /v1/floods, /v1/floods/dismiss, /v1/mutes with floods.changed and mutes.changed events; view devices can read them, changing them needs decide
  • Rules can dismiss info notices with action: dismiss (matching only kind: info; recorded as dismissed with decided_by: rule). Other rule actions still never touch notices

Changed

  • Inbox and History filters in the desktop app, Android app and web inbox: pick several type or outcome chips on one sideways-scrolling row, then narrow by agent, request type or files in Filters. Nothing picked shows everything; extra picks stay visible as removable chips, and a reset icon clears them. Unread automatic decisions open through the auto-handled · View line above the list instead of a tab
  • The selected request in the Inbox and History lists stands out more, with a blue bar and outline, so it no longer looks like a hovered row
  • Every stored file preview (Markdown, text, code, JSON, HTML, diff, plain file, as well as images, video, audio and PDF) now shows one compact row above it in the desktop app, Android app and web inbox: name · type · size with icon-only Open and Download buttons (HTML has Download only). Bundle files use the same row, and code previews no longer repeat the file name in their toolbar. Inline text sent without a file has no file actions

Fixed

  • Auto-handled Mark read now syncs across every device connected to the same daemon, separately for each computer, and survives daemon restarts. GET/PUT /v1/auto-read max-merges the read watermark and emits auto_read.changed only when it rises; remote and relay updates require decide scope
  • omp turn notices now come only from interactive omp sessions: headless runs (omp -p, scripted evals; detected through omp's ctx.hasUI) no longer send their final message as a handup notice, which flooded the inbox when tools launched many omp runs
  • Phone inbox (Split layout): when the request you last opened is resolved on another device while you are on the list screen, the list moves on and shows All clear after the last one, instead of an empty list

v0.1.6

2026-10-07

Added

  • omp and Claude Code can show each finished turn's final message as a handup notice: turn it on with handup config set integrations.omp.turn_notice true (then restart omp; needs the handup omp extension) or integrations.claude.turn_notice true (the Stop hook handup integrate claude installs reads it at once; no hook to write). Typing a reply sends it back to the agent and wakes it; OK or Dismiss does nothing. omp subagents and turns that already sent you a handup request get no notice; in Claude Code, sessions already waiting on an approval or question get none. A new advanced guide shows how to wire the same idea, and other lifecycle hooks, into Codex, Cursor, Gemini CLI and opencode

v0.1.5

2026-10-06

Fixed

  • Opening History no longer crashes the app when the daemon reports an outcome this app version doesn't know (for example an older Android app or web inbox talking to a newer daemon); unknown outcomes show as a plain badge
  • If a screen hits an unexpected error, handup now shows Something went wrong with the error, Back to Inbox, Reload and Report a bug, and keeps the header, instead of a blank page

v0.1.4

2026-10-06

Added

  • Windows x86-64 beta release downloads: handup_<version>_windows_amd64.zip (CLI and daemon, with the browser web inbox) and the per-user, unsigned desktop installer handup-desktop_<version>_amd64-setup.exe, listed in checksums.txt and releases.json (os windows, kinds archive and nsis). The daemon serves local clients on its token-gated loopback listener instead of a Unix socket; run handup serve (handup service install is not supported on Windows); handup's data and state directories and the bearer token get an owner-only ACL (you, SYSTEM and Administrators, inheritance off): a directory you own that others can read is locked down, one owned by someone else is refused, and handup doctor reports state directory and token privacy; the desktop app talks to that listener, starts the bundled daemon and cannot run commands from a request on Windows; handup ui finds the installed handup-app.exe. Maintainers build them with make release-windows on the public repo's free windows-build workflow, which smoke-tests the CLI, daemon, silent install, app launch (screenshot) and uninstall on real Windows; make release (hybrid and Modal-only paths) dispatches it on the release tag, downloads its files and assembles with WINDOWS=1 (WINDOWS=0 skips Windows)

v0.1.3

2026-10-05

Fixed

  • Linux: the daemon no longer refuses to start after the v0.1.2 install script ("~/.local/share/handup must be a private directory with mode 0700"); the installer keeps that directory private, and rerunning it repairs an existing install

v0.1.2

2026-10-05

Fixed

  • Browser web inbox files now ship in CLI archives, Linux packages and desktop bundles from v0.1.2, so browser pairing works after a normal installation

v0.1.1

2026-10-05

Added

  • Release archives, Linux packages and the desktop app ship THIRD_PARTY_NOTICES.md with full license texts for bundled Rust crates and UI packages
  • handup setup [--dry-run] runs first-run setup in one command: installs and starts the user service, connects every detected agent (integrate all) and installs the agent skill, then checks the daemon; safe to rerun (it leaves an active service alone and skips the service when a daemon already runs outside it), and install.sh and macOS release notes now point to it

Changed

  • deb, rpm and Arch packages list ariel@gethandup.dev as maintainer
  • handup service install backs up a changed service file to .bak before replacing it, and on macOS reloads the running job when its definition changed

Fixed

  • Claude Code notice replies now reach the agent on their own: MCP notices default to Claude's session, and the Stop hook runs in the background with asyncRewake, waking an idle session when the human replies; rerun handup integrate claude to upgrade the Stop hook
  • install.sh compares full SemVer, so release candidates upgrade to the final release
  • The desktop main window fits the current monitor's work area instead of opening at 1440x900 on small screens
  • handup ui --next on macOS reaches an already running app and opens the quick window

Security

  • Release checksums are signed with minisign, and install.sh verifies the signature when minisign is installed (SHA-256 only otherwise, or for older releases)
  • FCM/APNs provider responses are capped at 64 KiB and OAuth token lifetimes are clamped; push and license endpoints refuse plain HTTP except on loopback
  • Redaction now catches handup device/submit tokens, license keys and labeled relay secrets while keeping sha256 content hashes intact; the daemon database and its WAL/SHM files are created and repaired as 0600
  • BREAKING: relay URLs over plain HTTP are allowed only on loopback and Tailscale addresses by default; LAN addresses need remote.relay.allow_plaintext_lan: true
  • The self-hosted relay rejects missing or malformed channel credentials before any database lookup and briefly caches unknown channels; relay-supplied diagnostics are redacted, stripped of control characters and truncated
  • Remote event WebSockets are capped at 16 per paired device (matching the relay's per-device limit) and close after two heartbeat intervals without a Pong

v0.1.0

2026-10-05

Added

  • Configurable storage limits: optional best-effort history.max_bytes live-storage cap (used database pages, WAL and blobs), history.files_keep_days file-only expiry, and previews.max_file_bytes upload/preview limit; uncapped total storage by default.
  • handup config, version, completion and uninstall commands
  • Core approval queue with validated preview snapshots, content-bound decisions, JSON Schema, and credential redaction.
  • Persistent SQLite WAL daemon with append-only audit history, atomic blob storage, authenticated API v1, WebSocket events, expiry, and Range downloads.
  • Agent-facing ask, wait, status, cancel, ls, show, approve, deny, and schema commands with stable decision exit codes and bounded daemon autostart.
  • Native desktop notifications, user-service management with dry-run, sample preview requests, and doctor diagnostics.
  • Agent integration: MCP approval/question/polling tools, Claude PermissionRequest hooks with fail-closed feedback, safe client installers, prompt snippets and AI-facing guides
  • Desktop app (Tauri v2): keyboard inbox for every preview type, tray pending count, quick window, sandboxed HTML previews; AppImage/deb bundles
  • handup ui [--next] opens the desktop inbox or the quick window
  • GET /v1/ui-settings for desktop and web UI settings
  • handup demo seeds a request for every preview type
  • Ordered fail-closed approval rules, scoped allow, auto-handled audit, native-fallback presence routing, and a live terminal inbox.
  • Codex PermissionRequest hook with denial feedback, session-scoped approvals, and a reversible CODEX_HOME installer
  • omp preflight approval and question-dialog extension with native UI races, configurable tool filtering, and a reversible agent-dir installer
  • Remote access: handup pair QR pairing with one-time codes, hashed scoped device tokens, handup devices list/revoke, tailscale and TLS-only direct modes, and a mobile web UI served by the daemon
  • ntfy notification backend that sends only the title and a deep link by default
  • Android app: pairs by QR or pasted link, keeps the device token in Keystore-sealed storage, pins TLS to the pairing fingerprint, asks for biometrics before high-risk approvals, and opens handup://r/<id> links
  • End-to-end encrypted relay: self-hostable handup-relay server forwarding only Noise_IK ciphertext, remote.relay daemon settings, handup pair --relay, Android app pairing and live queue through the relay, and content-free FCM HTTP v1/APNs wake-ups
  • Desktop app Pair a phone dialog: pairing QR code with scope and relay choice, expiry countdown, New code, copyable link, TLS fingerprint, Paired confirmation, and setup guidance when remote access is off; GET /v1/ui-settings reports pair_relay
  • Decision history on desktop and mobile: Inbox | History switch (h), day-grouped resolved requests with search and outcome filters, read-only detail with decision summary and audit trail; GET /v1/history and GET /v1/requests/{id}/audit; bounded retention (history.keep_days 90, history.max_requests 2000, pending never pruned) with a doctor storage check
  • Native Android notifications: POST_NOTIFICATIONS prompt after pairing, Approval requests and High-risk channels, Firebase Messaging data pushes that open the request and clear when it is resolved, a Notifications row in mobile settings; daemon fcm backend (notifications.fcm service_account, payload title|wake) and PUT/DELETE /v1/devices/self/push
  • Offline-tolerant inbox: the last queue stays visible with a Live / Syncing… / Offline · synced hh:mm connection pill (tap to resync), a cached queue for offline cold starts, and an outbox that sends decisions made offline in order with Undo and per-item refusal reasons
  • Display settings: Compact (default) detail shows risk, content, expiry, agent and location as small tabs on the preview card; Comfortable keeps cards; Ultra-compact also trims list rows; System/Light/Dark theme; saved per device on desktop, web and mobile
  • Android: high-risk biometric prompt at tap time, Back closes dialogs and returns from detail to the list, keyboard no longer covers inputs, handup launcher icon (adaptive and monochrome), Copy buttons on command/text/code previews, unified diffs on narrow screens
  • Color palettes in Display settings: handup (default), Catppuccin, Gruvbox, Solarized, Rosé Pine, GitHub, Everforest, Tokyo Night, Nord, One, Kanagawa, Ayu, Flexoki and Dracula, each with a light and dark variant that follows the theme; code highlighting in diffs and file previews follows the palette; saved per device on desktop, web and mobile
  • Desktop app Display option Focus on new requests (off by default) raises the quick window or inbox when a new pending request arrives, independent of desktop notifications
  • YOLO mode: handup yolo on|hard|off [--for 1h], GET/PUT /v1/yolo and an inbox header switch auto-approve new low and medium (hard: every) risk requests; runtime only, default off, rules win and questions/forms stay pending
  • Event hooks: top-level hooks: with signed webhook sinks (json envelope or Slack/Discord/Teams/custom template bodies) and exec hooks (event JSON on stdin) for request created/decided/expired/cancelled/reminder/expiring, per-hook event filters, handup hooks list|test, and a generated event schema
  • Named submit-only integration tokens: handup tokens create/list/revoke; token holders can create and poll only their own requests, verified integration identity shown in the UI (via <name> · verified), optional expiry and per-token creation rate limit; GitHub Actions and n8n recipes
  • Structured questions: input.questions (header, options with descriptions/previews, recommended, multi, free text) answered by a keyboard-first question form (option cards, Recommended badge, previews, Other text, Tab/arrows/1–9, Ctrl+Enter submit) with Raw JSON behind a toggle; answers in decision.fields.answers; handup answer --select/--text; omp ask and MCP ask_question use it
  • Per-request callback_url: signed decided/expired/cancelled envelopes POSTed to hosts in the required callbacks.allowlist (https, or loopback http), reusing hook signing and retries; handup ask --callback-url; callback.delivered/failed audit rows
  • Per-device controls without re-pairing: handup devices disable|enable|mute|unmute <id>, handup devices scope <id> view|decide, and local PATCH /v1/devices/{id} {enabled, push, scope}; a disabled device is refused (HTTP 403, WebSocket close 4403, no push, no previews) until re-enabled with the same token, a muted device keeps access but gets no FCM push or relay wake, and a scope downgrade applies to open connections at once
  • handup doctor warns when the daemon runs a different build than the CLI (new local GET /v1/version) and lists handup processes, such as handup mcp servers, still running a binary replaced on disk
  • handup ask --help describes every flag, including the --preview TYPE:PATH and --option ID:LABEL[:OUTCOME] syntax
  • Desktop Devices panel (m or the header button): list paired phones and browsers with enable/disable, push on/off, Can decide/View only, and Revoke with confirmation
  • Mobile swipe cards: swipe right to approve or left to deny anywhere on the approval card, including previews; the card tilts and flies off after a short drag or quick flick with an Approve/Deny stamp and springs back otherwise; buttons and safety gates remain, and a Swipe cards switch in Settings turns swiping off
  • Read Markdown attachments in-app with safe GFM rendering, syntax-highlighted code fences, and mobile-local table/code scrolling.
  • Files previews: per-file size and type, open and download for every file, checkbox selection with select-all, and multi-file download as one .zip; desktop saves into Downloads without overwriting
  • JSON previews are syntax-highlighted with the active palette, with a Tree view toggle
  • Left-handed layout: decisions.primary_side (right|left, default right) mirrors every decision row for all clients via GET /v1/ui-settings, and Display → Approve button side (Default/Right/Left) overrides it per device; swipe right still approves
  • Email draft approvals: email preview type (headers, attachment chips, Markdown body, collapsed quoted reply, sandboxed HTML), an email Edit & approve form when the draft is passed as input {to,cc,bcc,subject,body} with edits returned in decision fields, handup ask --preview email:FILE, MCP email previews, a demo email request and an agent guide; handup never sends mail
  • Open in mail app on email previews (inbox and History): opens the shown or edited draft in your own mail client via a mailto: link (desktop system handler, Android mail app, browser); it does not change the request and handup still never sends mail
  • omp extension gated tools come from handup config integrations.omp.tools ([] gates none; HANDUP_OMP_TOOLS overrides), so the installed extension never needs hand edits
  • Mobile app pairs with several computers: one inbox merges their requests with a host chip per computer (hidden with one), decisions, previews, attachments, history and audit go to the request's own computer, per-computer offline chips and outbox so one computer down never blocks another, Settings lists computers to rename, unpair one, or Pair another computer
  • omp extension pushes pending MCP and CLI request decisions into the session, waking idle agents with feedback and question answers while suppressing decisions already observed inline.
  • Mobile offline copy: History, request details, audit trails and opened files are cached per computer in app-private SQLite (last 30 days, everything read, or off; 256 MB cap with least-recently-used eviction) and shown offline marked Offline copy from <time>; optional Wi-Fi-only prefetch of new requests' files up to a size; opt-in Android 10+ autosave of request files to Download/handup once per file with a per-file limit; per-computer Clear cache, and unpairing deletes that computer's copy. The pending queue, writes, settings, pairing and tokens are never cached
  • History filters for Files (any attachment preview) and request kind; GET /v1/history has=attachments and kind; past attachments keep Open/Download
  • MCP requests record the client as agent (initialize clientInfo), the client's workspace root as cwd, and optional session/session_title; omp sends its session name; UI shows the session title
  • MCP wait_requests tool: pass every pending request id and get decisions (with feedback and answers) back as soon as any is decided, or after max_wait_seconds (default 25, max 300); blocking request_approval/ask_question calls now stop after 300s with status pending, and pending results tell agents to keep calling wait_requests before ending the turn, so agents without a shell wait still receive every answer
  • MCP cancel_request and list_requests tools, request_approval on_timeout/dedupe_key/callback_url, GET /v1/requests?session= and handup ls --agent/--session/--limit, so MCP-only and custom harnesses can retract requests and recover ids
  • Android keeps queued phone decisions across restarts and sends them in the background with a silent notification while they wait.
  • Inbox layouts in Display settings, each with a preview: Split (default), Stacked (list above the request, phones too), Focus (one request at a time with ‹ › and a Queue sheet) and Rail (icon rail on desktop, chip strip on phones); drag the Split/Stacked divider with mouse or touch to resize (arrow keys too), double-click/double-tap or Reset sizes to restore; saved per device
  • Read aloud: a play/stop button reads the current request or the whole queue (agent, title, summary, risk and each option) with the device's voices; Voice settings per device for voice (searchable picker), speed, pitch, scope, options and auto-reading new requests (Linux needs speech-dispatcher)
  • Desktop app Run for command requests: runs the exact requested command locally (leading sudo via the polkit/macOS admin prompt, never a stored password), streams output with Cancel and a 10-minute timeout, and returns a redacted run_result (exit code, output tails, duration, human feedback) to the agent; while it runs the daemon holds a run claim so no other surface can decide it, and CLI waits exit 5 (ran in the desktop app) so shell gates do not run it again. Phones, the web UI and the relay never run commands and cannot submit run results.
  • Settings: Send test request / Send test question put a harmless synthetic request in the queue (desktop to the local daemon, mobile per paired computer) via POST /v1/requests/test (decide scope for paired devices)
  • Settings grouped into collapsible Appearance, Decisions, Read aloud and Test sections with value summaries; the desktop/web dialog is now Settings
  • Desktop app: Ignore input after focus (Off/0.5s/1s/2s, default 1s) under Focus on new requests swallows keys and clicks briefly after the window takes focus, and keeps a held key ignored until release, so typing meant for another app cannot approve or deny
  • Desktop Run: R hotkey, footer Output button after a run, and Settings → Decisions → Show output after Run (never/on failure/always)
  • Binary downloads on GitHub Releases (gethandup/handup): .deb, .rpm, Alpine .apk and Arch pacman packages, a Homebrew cask (gethandup/homebrew-tap), a checksum-verifying no-sudo install.sh, a releases.json manifest, a /downloads page that detects your platform with copyable install commands, and a /changelog page
  • Mobile: Settings → Previews → Auto-download media (Always, Wi-Fi only by default, or Never) downloads new pending requests' files in the background, up to 50 MB per request by default, so previews open at once; files are dropped when the request is decided, expires or is cancelled unless you opened them
  • Storage settings on desktop, web and mobile: disk use by category (history, audit log, files, free space), keep-history and keep-files periods saved to the daemon config, and Clean up now (older than 1 day/1 week/1 month/3 months, keep newest 100, or all) with an inline estimate and confirmation; cleanups run in the background on the daemon in small batches with progress and Stop, then compact the database. Decide-scoped phones and paired browsers can clean up and change retention too; phones also clear their own offline copy. CLI: handup storage [--json] and handup storage clean history|files (--older-than|--keep|--all); API: /v1/storage, /v1/storage/estimate, /v1/storage/cleanup (keep_newest), /v1/storage/retention
  • Decide hooks: a decide: hook runs a local policy program on request.created for pending requests matching optional tool/kind/agent filters and may approve, deny or answer them (stdout JSON {option, feedback?, fields?}, hash-bound, decided_by hook:<name>; empty output or any failure leaves the request pending); handup hooks test dry-runs it, GET /v1/requests?decided_by=hook: lists hook decisions, and History/Auto-handled show them as automatic. Hook entries take exactly one of decide:, exec: <program> or webhook: <url>, with an optional name (defaults to the program stem or URL host).
  • handup skill install installs the agent skill bundled in the binary (offline and version-matched, for generic, Claude or Codex skill folders, user or project scope); handup skill uninstall removes only copies it installed.
  • Report a bug or request a feature from Settings → Help & feedback (desktop, Android, web UI) or handup report: they open a prefilled support form on gethandup.dev
  • Info notices: MCP notify (title, summary, previews, session, timeout, dedupe_key) and handup ask --kind info send information that needs no reply; it returns at once and wait_requests lists notice ids under notices, never pending. A notice expires on timeout, takes no input and is never decided by rules or YOLO, and resolving it keeps the usual Undo. New status dismissed (exit 0) and History outcome/kind filters Dismissed and Info; ask_question and request_approval point to notify for pure information.
  • Info notices offer OK (primary, approve outcome) and Dismiss (deny outcome), both recorded as status dismissed with decision.option ok or dismiss: buttons, a/d, swipe right/left, OK/Dismiss desktop notification actions and the terminal inbox approve/deny keys pick them
  • Markdown code blocks in previews, summaries and questions have a Copy code button in their top-right corner (on hover or focus with a mouse, always visible on touch).
  • Settings → Keyboard shortcuts (collapsed by default): the keys this device can use, grouped Navigate, Decide, Questions and View, plus swipe and tap gestures on touch screens and in the Android app. With a fine pointer and local or paired decide access, select a shortcut and press a new key (Esc cancels); conflicts are refused inline, with per-shortcut Reset and Reset all. Touch/coarse-pointer and view-only devices keep a read-only list. Overrides save to the computer: Settings changes apply live to connected clients, the shortcut sheet and button hints. Or use handup config keys and handup config set keys.approve shift+y, which also applies live. Unknown actions, bad keys and per-view conflicts are rejected; built-in keys and terminal inbox keys stay fixed.
  • Reply to agent notices with optional feedback: replies arrive automatically in later handup tool results (even without a session ID), live in omp, and through Claude Code prompt and stop hooks; empty dismissals stay silent.
  • One live config.yaml: the daemon watches its config file and applies every valid save (editor, handup config set, Settings, PUT /v1/keys, PUT /v1/storage/retention) without a restart, including a save made while it starts; only daemon.listen, previews.max_file_bytes and remote.* need one. An invalid save (including rule ids that collide with a session rule) keeps the previous config and logs path:line: message. Each save emits a config.changed event (applied, restart_required, error), GET /v1/ui-settings adds config_restart_required and config_error, local-only GET /v1/config/status reports the watched file and the content it last handled, and desktop, web and mobile show an inline header notice (its own row on narrow screens): Config reloaded, Restart daemon to apply: … (a dismissed notice returns when that list changes), or config.yaml invalid (line N), kept previous
  • handup config unset restores a default, config validate [path] checks a file with the daemons validation, config show --effective [--json] lists every setting with its file/default source (client preferences output_format, no_color and display.theme included), and get/keys accept --json; set/unset/toggle/edit write through the validated atomic writer (synced to disk, through a symlinked config.yaml to its target) and report Applied live only once the daemon watching that same file has handled the save, otherwise that it needs a restart, kept its previous config, uses a different config file, or has not picked up the save yet; config edit re-opens or discards an invalid edit
  • Stop a desktop Run from anywhere: phones, the browser and other desktop windows show a running command's elapsed time and limit with a Stop button (hidden on view-only devices) that shows Stopping… and who asked; handup stop <id> [--json] and POST /v1/requests/{id}/run/stop (local, remote and relay; decide scope for paired devices; audited as run_stop_requested) record run.stop, and the desktop running it (checking every second) stops the command and reports stopped from <device>
  • Desktop Run limits: run.timeout (default 10m, applies live) and run.max_timeout (default 1h) config keys, and a per-request run_timeout (MCP request_approval, request JSON, handup ask --run-timeout) capped at run.max_timeout; all three must be between 1ms and one year; the run claim records timeout_ms and its lease is the limit plus 2 minutes; a running command shows elapsed time and the limit, and after 30s without output a No output notice explains that input is closed and how to stop it
  • handup integrate all safely configures every detected agent, with per-agent summaries and reversible uninstall
  • Paid license after a 14-day trial in release builds: handup license status|activate|import|deactivate exchanges the Polar license key once at gethandup.dev for an Ed25519-signed perpetual license that then works offline; Settings → License (days left, paste key, import file, Buy) and an inline banner in the last 3 trial days or when locked; GET/PUT/DELETE /v1/license; Polar rechecks every 3 days lock only on a definite revoked/disabled/unknown key. A locked daemon refuses new requests (exit 6, HTTP 402 license_required, same MCP code), hooks fall back to the agent's own prompt, and pending requests, history, settings and uninstall keep working
  • Settings › About shows the app version, commit, build date, platform, the daemon's version and the license state, with one Copy for bug reports
  • Requests wait for a human by default (no timeout, expires_at null); agents or config opt into a timeout with timeout/--timeout and on_timeout. Pending reminders are opt-in via notifications.remind_every (off by default)
  • Undo: decisions.undo_window (default 5s) with a per-device Display → Undo window override (3s–30s); every decision still waiting keeps its own countdown and Undo (the newest in the footer, older ones stacked above it), and repeated u/Undo walks back each one
  • handup integrate claude|codex|omp installs that agent's hook or extension plus its handup MCP server (--no-mcp to skip the MCP server), checks every file before writing any, and for Claude allows mcp__handup so the hook does not ask about handup's own requests
  • handup ships as binaries under a personal license; first-party source is proprietary
  • Rules live under rules: in config.yaml (project and always allow rules and handup rules rm write there) and apply when the config is saved; an invalid file keeps the previous rules
  • MCP server works with Claude Code, Codex, Gemini CLI and other MCP clients, accepting protocol versions 2024-11-05 through 2026-07-28

Security

  • The self-hosted relay requires a registration token of at least 32 characters and refuses channel creation from locked-out IPs, even with the correct token, until the failure window expires.