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
## Summary
- reconcile deterministic lifecycle mux sessions with token-backed
ownership, adopting only healthy owned sessions and safely replacing
stale owned sessions
- include a deterministic SHA-256 fingerprint of the effective overlay
and sorted lifecycle env in freshness, so changed env replaces stale
host helpers without persisting names, keys, or values
- preserve running Compose services, lifecycle helpers, and ownership
state when a down.before guard aborts hack down or hack restart
- target detached Zellij sessions through the documented global
--session selector for owner metadata, inspection, pane creation, exec,
and input
- teach doctor to detect and safely repair established ownership-proven
orphan sessions while preserving foreign collisions and recent in-flight
startups
- make the local tmux recovery scenario capability-aware while requiring
tmux in the dedicated Docker/tmux CI job
## Verification
- 850 unit tests passed, 5 intentional skips, 0 failed
- typecheck, privacy check, canonical Turbo quality checks, changed-file
Ultracite, build, generated agent parity, and CLI reference generation
passed
- full local E2E with tmux mandatory: 9 local scenarios passed; 3
disabled Docker-tier scenarios skipped
- lifecycle recovery E2E proves env refresh, failed down/restart guard
preservation, ownership safety, signal cleanup, and doctor repair
- compiled binary passed the lifecycle session recovery E2E scenario
- no-tmux recovery E2E exits 0 with an explicit local skip and exits 1
with zero skips when HACK_E2E_REQUIRE_TMUX=1
- live Zellij 0.43.1 detached-session probe wrote and read owner
metadata through --session and cleaned up successfully
- release tarball extracted and its binary passed version smoke
- current-head CI passed secret scan, test/release smoke, runtime
images, and non-skippable Docker/tmux E2E
- local Docker 29.4.0 responds to info but hangs on container creation;
Docker scenarios are proven by current-head CI
## Release
Release signal: yes. This is a user-visible lifecycle reliability fix
and should produce a patch release through semantic-release. No separate
changeset is used by this repository release flow.
Copy file name to clipboardExpand all lines: .codex/skills/hack-cli/SKILL.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,6 +113,8 @@ Use `hack` as the primary interface for local-first development.
113
113
- For fixed-port host helpers such as SSM tunnels or local proxies, set `singleton.ports` and usually `onConflict: "adopt"` so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.
114
114
-`singleton` is a listener guard, not process ownership transfer; adopted external processes are left running on `hack down`.
115
115
- Inspect lifecycle status via `hack projects --details` and stream via `hack logs <service-or-process>`.
116
+
- Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
117
+
-`hack doctor --fix` reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.
116
118
117
119
## Workspaces (mux-managed, tmux-first by default)
Copy file name to clipboardExpand all lines: .cursor/rules/hack.mdc
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -61,6 +61,8 @@ Prefer `hack` when shell access is available. Use MCP only when shell access is
61
61
- For fixed-port host helpers such as SSM tunnels or local proxies, set `singleton.ports` and usually `onConflict: "adopt"` so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.
62
62
- `singleton` is a listener guard, not process ownership transfer; adopted external processes are left running on `hack down`.
63
63
- Inspect lifecycle status via `hack projects --details` and stream via `hack logs <service-or-process>`.
64
+
- Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
65
+
- `hack doctor --fix` reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.
Copy file name to clipboardExpand all lines: AGENTS.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -299,6 +299,8 @@ Lifecycle + startup:
299
299
- For fixed-port host helpers such as SSM tunnels or local proxies, set `singleton.ports` and usually `onConflict: "adopt"` so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.
300
300
-`singleton` is a listener guard, not process ownership transfer; adopted external processes are left running on `hack down`.
301
301
- Inspect lifecycle status via `hack projects --details` and stream via `hack logs <service-or-process>`.
302
+
- Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
303
+
-`hack doctor --fix` reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.
302
304
303
305
Workspaces (mux-managed, tmux-first by default):
304
306
- Picker: `hack session` for persistent project workspaces.
Copy file name to clipboardExpand all lines: CLAUDE.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -169,6 +169,8 @@ Lifecycle + startup:
169
169
- For fixed-port host helpers such as SSM tunnels or local proxies, set `singleton.ports` and usually `onConflict: "adopt"` so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.
170
170
-`singleton` is a listener guard, not process ownership transfer; adopted external processes are left running on `hack down`.
171
171
- Inspect lifecycle status via `hack projects --details` and stream via `hack logs <service-or-process>`.
172
+
- Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.
173
+
-`hack doctor --fix` reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.
172
174
173
175
Workspaces (mux-managed, tmux-first by default):
174
176
- Picker: `hack session` for persistent project workspaces.
Copy file name to clipboardExpand all lines: docs/lifecycle.md
+29-1Lines changed: 29 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -179,16 +179,35 @@ Lifecycle output is now surfaced across CLI/runtime views:
179
179
5. Run `docker compose up` (or `up -d` when `--detach`).
180
180
6. Run `lifecycle.up.after` hooks.
181
181
182
+
Before starting persistent work, Hack reconciles the expected lifecycle session with
183
+
`.hack/.internal/lifecycle/state.json`:
184
+
185
+
- A session whose mux ownership token, saved definition hash, and live process windows all match is
186
+
adopted without starting duplicate processes.
187
+
- A token-owned stale session is replaced. Pre-ownership tmux sessions are replaced only when saved
188
+
state, checkout path, creation time, and window count provide deterministic legacy ownership proof.
189
+
- A same-name session without matching ownership proof is left untouched and `hack up` fails with an
190
+
actionable collision error.
191
+
192
+
If the operation later fails in Compose or `up.after`, Hack removes only the lifecycle session created
193
+
by that operation. The same exact-token cleanup runs for supported `SIGINT` and `SIGTERM` paths, including
194
+
signals received while the mux session is still being initialized.
195
+
182
196
### `hack down`
183
197
184
198
1. Run `lifecycle.down.before` hooks.
185
199
2. Run `docker compose down`.
186
200
3. Stop lifecycle processes by killing the lifecycle session.
187
201
4. Run `lifecycle.down.after` hooks.
188
202
203
+
If `lifecycle.down.before` fails, shutdown is aborted before Compose or lifecycle processes are
204
+
stopped. `hack restart` preserves the same guard semantics during its down phase.
205
+
189
206
### `hack restart`
190
207
191
208
`hack restart` performs the same lifecycle steps as `hack down` followed by `hack up`.
209
+
From the primary checkout, it targets only the base Compose/lifecycle instance. A linked worktree uses
210
+
its isolated derived branch instance, and `--branch <name>` targets only that explicit branch.
192
211
193
212
### `--json`
194
213
@@ -212,10 +231,19 @@ Lifecycle session name:
212
231
Notes:
213
232
- If no mux backend is available, lifecycle process startup fails with an actionable error.
214
233
- Teardown is implemented by killing the lifecycle session; anything running inside that session will be stopped.
234
+
- Current lifecycle sessions carry the same random ownership token in mux metadata and persisted state.
235
+
Cleanup requires an exact token match; deterministic names alone never authorize session teardown.
236
+
- Healthy-session adoption also matches a stable SHA-256 fingerprint of the effective overlay and
237
+
sorted lifecycle environment. Environment names, keys, and values are never persisted; changing an
238
+
overlay or value replaces the owned session so host helpers cannot retain stale configuration.
215
239
- For tmux-backed lifecycle sessions, Hack also persists the pane PID and the wrapped command's actual
216
240
process-group metadata to `.hack/.internal/lifecycle/state.json`, with per-hook/process output logged to
217
241
`.hack/.internal/lifecycle/*.log`. If tmux pane state disappears before teardown, `hack down` still uses that persisted metadata to clean up any live lifecycle process groups instead of leaving orphaned host processes behind.
218
-
-`hack doctor` reports stale lifecycle state when the persisted lifecycle entry no longer has a live mux session and points operators to `hack down` so cleanup and state removal happen through the supported path. `hack doctor --fix` does not tear down lifecycle sessions itself — use `hack down` for that.
242
+
-`hack doctor` reports stale lifecycle state, leaderless process groups, ownership collisions, and
243
+
ownership-proven sessions with no running Compose instance. Recently updated sessions are treated
244
+
as possible in-flight startups for five minutes and stay untouched. `hack doctor --fix` reaps only
245
+
established orphans after rechecking runtime liveness and mux ownership; unverified same-name
'For fixed-port host helpers such as SSM tunnels or local proxies, set `singleton.ports` and usually `onConflict: "adopt"` so Hack reuses a healthy existing listener instead of starting duplicate tunnel stacks.',
186
186
"`singleton` is a listener guard, not process ownership transfer; adopted external processes are left running on `hack down`.",
187
187
"Inspect lifecycle status via `hack projects --details` and stream via `hack logs <service-or-process>`.",
188
+
"Lifecycle session recovery is ownership-proven: Hack adopts healthy token-, definition-, and environment-matched sessions, replaces owned stale sessions, and refuses to kill same-name sessions without deterministic ownership proof.",
189
+
"`hack doctor --fix` reaps an orphan lifecycle session only when mux ownership is proven and its Compose instance is absent; unverified same-name sessions are never modified.",
0 commit comments