Sessions
Sessions are persistent project workspaces. They keep a repo-root shell alive across terminal restarts, SSH reconnects, and long-running agent work so you can return to the same context instead of rebuilding it each time.
The guided default is tmux:
hack sessionopens a tmux-first interactive pickerhack session start <project>reuses the default project workspace unless you ask for an isolated onehack session start <project> --env qa --service apicreates a stable env-scoped workspace when you need a specific overlayhack setup tmuxinstalls the recommended popup binding so the picker is easy to reach from tmux
Other mux backends still exist. sessions.mux=auto prefers tmux and falls back to zellij when tmux is unavailable, and you can explicitly set sessions.mux=zellij if that is your preferred backend. The interactive onboarding and pane tooling are tmux-first today.
Quick start
# Interactive picker for existing workspaces and new project workspaces
hack session
# Reuse the default workspace for a project, or create it if needed
hack session start <project>
# Create an env-scoped workspace for one overlay/service combination
hack session start <project> --env qa --service api --detach
# List active workspaces
hack session list
# Attach to an existing workspace by name
hack session attach <workspace>
# Queue a command in a running workspace
hack session exec <workspace> "bun test"
# Queue a command with injected env for that run
hack session exec <workspace> --env qa --service api "bun db:migrate"
# Stop a workspace
hack session stop <workspace>
# Capture recent output (defaults to active pane)
hack session capture <workspace>
# Tail workspace output for a short window (defaults to active pane)
hack session tail <workspace>
# List panes in a workspace
hack session panes <workspace>Core flows
Interactive picker
Running hack session without arguments opens an interactive picker showing:
- Active workspaces: running workspaces from the configured mux backend(s), with attached status when the backend can report it
- Available projects: registered projects that do not currently have a default workspace
When selecting an attached session, you can choose to:
- Attach: detach other clients and take over the workspace
- Create isolated: start a separate long-running workspace such as
project--2
This is the recommended entry point when you want to resume a project context but do not remember the exact workspace name.
hack session with no subcommand is inherently interactive (it prompts with a picker) and is not a
scripted/agent entry point. For scripted or agent-driven runs, use hack session start <project>
(optionally with --detach) or hack session list --json instead of the bare picker.
Start and create
# Reuse the default project workspace (creates it if needed)
hack session start my-project
# Force creation of an isolated numbered workspace
hack session start my-project --new
# Create an isolated workspace with a custom suffix
hack session start my-project --name agent-1
# Creates: my-project--agent-1
# Prepare services before creating or attaching
hack session start my-project --up
# Create or reuse without attaching the current terminal
hack session start my-project --detach
# Create a stable env-scoped workspace
hack session start my-project --env qa --service api --detachDefault behavior is deliberate:
- The first workspace for a project is just the project name, for example
my-project - Passing
--envor--servicecreates a stable scoped workspace name such asmy-project.env-qa.svc-api --newor--namecreates an isolated workspace for a second agent, experiment, or review lane--detachkeeps the workspace alive for another tool or terminal to attach later
Workspaces are created at the project's repo root, not the .hack/ directory.
Attach
# Attach to an existing workspace
hack session attach my-project
# From inside tmux, this switches clients instead of nesting tmuxUse attach when you already know which workspace you want. If you are already inside tmux, hack switches clients instead of nesting tmux inside tmux. When attaching, tmux detaches other clients to avoid terminal size conflicts across multiple devices.
If the workspace is running in zellij, hack attaches to that named session directly instead.
Stop
hack session stop <workspace>stop kills the underlying mux session (backend.killSession) outright — anything still running
inside it, including a long-lived dev server or an in-progress agent task, is terminated. There is
no graceful shutdown step; if you need cleanup hooks to run first, do that before stopping.
Exec
# Send a command to a running workspace
hack session exec my-project "bun test"
# tmux: sends the command + Enter to the active pane
# zellij: opens a new pane and runs the command because there is no active-pane equivalent
# Inject a selected overlay/service into the queued command
hack session exec my-project.env-qa.svc-api --env qa --service api "bun db:migrate"Use exec when you want to drive a persistent workspace from another terminal, script, or agent without attaching interactively first.
If you pass --env or --service, Hack resolves the selected project env and injects it into the queued command without materializing .hack/.env.
Listing panes (tmux only)
Use hack session panes to list panes with useful metadata (target, active flag, window/pane indices, command, path).
# List panes (NDJSON start/log/end)
hack session panes my-project
# Explicit NDJSON output (same as the default)
hack session panes my-project --json
# Single JSON object output
hack session panes my-project --pretty--json and --pretty are mutually exclusive; passing both is an error.
Capturing output (tmux only)
hack session capture emits NDJSON events by default for machine parsing (start/log/end). By default it targets the active pane; use --target to select a specific pane. Use --pretty for raw pane output; --json and --pretty are mutually exclusive.
# Capture last 200 lines (default) as NDJSON
hack session capture my-project
# Capture a specific pane target and line count (tmux pane targets use `:` and `.`)
hack session capture my-project --target my-project:0.1 --lines 500
# Human-friendly raw output
hack session capture my-project --prettyTailing output (tmux only)
hack session tail also emits NDJSON events by default and stops after --max-ms (default 5000), polling every --interval-ms (default 500). By default it tails the active pane; use --target to select a specific pane. --json and --pretty are mutually exclusive.
# Poll capture-pane and emit only new lines for 5s (default)
hack session tail my-project
# Customize polling interval and max duration
hack session tail my-project --interval-ms 250 --max-ms 10000
# Human-friendly raw output
hack session tail my-project --prettyRemote access with SSH
Use hack ssh to get connection info for remote access:
# Interactive: choose Tailscale or direct SSH
hack ssh
# Use Tailscale SSH
hack ssh --tailscale
# Use direct SSH with hostname
hack ssh --direct --host 192.168.1.100
# Specify user and port
hack ssh --host example.com --user dev --port 2222
# Connect directly to a workspace
hack ssh my-sessionWhat hack ssh shows
- SSH command: Copy-paste command to connect
- QR code: Scan with mobile SSH apps (Blink, Termius)
- Active workspaces: List of active workspaces on this machine, including the active mux backend when known
- Action picker: Done or connect to a workspace
When you connect to an existing workspace, hack uses that workspace's backend. When you create a new one from the SSH flow, hack uses the configured default backend (sessions.mux).
Tailscale setup
If Tailscale is stopped or not connected, hack ssh will prompt to start it:
Tailscale is stopped
? Turn on Tailscale? › yes
Starting Tailscale...
Tailscale connected!Terminal size conflicts
When multiple clients attach to the same tmux workspace, terminal size can get messed up. The session commands use tmux attach -d to detach other clients, avoiding this issue.
If you're already in tmux and want to switch workspaces without detaching:
# Inside tmux: prefix + s to open session switcher
# Or use the tmux command:
tmux switch-client -t <session>Configuration
Project config: .hack/hack.config.json
{
"sessions": {
"mux": "auto"
}
}Values:
auto(default): prefer tmux, fall back to zellijtmux: require tmuxzellij: require zellijnone: disable sessions
Env override: HACK_SESSIONS_MUX=auto|tmux|zellij|none
Global default:
hack config set --global sessions.mux zellijRecommended onboarding:
# Install the tmux popup binding that launches the picker
hack setup tmux
# Keep tmux as the guided default, but switch globally if your team prefers zellij
hack config set --global sessions.mux zellijSource: v5 (next.12) · docs/sessions.md.