pi-claude-supervisor 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented here.
4
4
 
5
+ ## [0.10.0](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.2...v0.10.0) (2026-09-24)
6
+
7
+
8
+ ### Features
9
+
10
+ * **policy:** delete/move floor beneath every permission authority ([#75](https://github.com/btnalit/pi-claude-supervisor/issues/75)) ([967f679](https://github.com/btnalit/pi-claude-supervisor/commit/967f6796503b28c58a6523ceae51d066f17d2b56))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **automation:** wait out provider outages; stop parking on binaries and symlinks ([#72](https://github.com/btnalit/pi-claude-supervisor/issues/72)) ([c2969e0](https://github.com/btnalit/pi-claude-supervisor/commit/c2969e08e4070cf977699f3b2bf90697c730cb9e))
16
+ * **hooks:** keep interactive supervision after the Worker changes directory ([#69](https://github.com/btnalit/pi-claude-supervisor/issues/69)) ([a46177a](https://github.com/btnalit/pi-claude-supervisor/commit/a46177a1769c180decef306f78418aa22be52216))
17
+ * **supervisor:** park a self-started verification that throws part-way ([#73](https://github.com/btnalit/pi-claude-supervisor/issues/73)) ([dfc546a](https://github.com/btnalit/pi-claude-supervisor/commit/dfc546a6871a4ed89f0b126661fa96436d5cac78))
18
+ * **supervisor:** stop a stray prompt from pausing unattended automation for good ([#74](https://github.com/btnalit/pi-claude-supervisor/issues/74)) ([7de0c6c](https://github.com/btnalit/pi-claude-supervisor/commit/7de0c6c09c58e20712659a3f24a97efc6eb704b3))
19
+ * **tmux:** deliver interactive input reliably instead of parking on a busy prompt ([#71](https://github.com/btnalit/pi-claude-supervisor/issues/71)) ([46083d2](https://github.com/btnalit/pi-claude-supervisor/commit/46083d27973261a0e51615e9234cb3f682b3a9fc))
20
+
21
+ ## [0.9.2](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.1...v0.9.2) (2026-09-24)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * **supervisor:** finish stop and startup races; add gated live Decision spike ([#67](https://github.com/btnalit/pi-claude-supervisor/issues/67)) ([648b269](https://github.com/btnalit/pi-claude-supervisor/commit/648b269f185fbeb5ab39fe22ce855cc8d4342747))
27
+ * **supervisor:** stability fixes found by end-to-end runs with a real Decision Worker ([#65](https://github.com/btnalit/pi-claude-supervisor/issues/65)) ([cd0b8ef](https://github.com/btnalit/pi-claude-supervisor/commit/cd0b8efdd0898833036b14acab5ca7b29ca70d04))
28
+
5
29
  ## [0.9.1](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.0...v0.9.1) (2026-09-24)
6
30
 
7
31
 
package/README.cn.md CHANGED
@@ -130,7 +130,10 @@ Pi 会在 Claude 结束一轮对话时(`Stop`;轮中 API/模型失败会以 `Sto
130
130
  **人机协同。** 如果你在已 attach 的 session 中输入内容,自动化会暂停
131
131
  (`human_takeover`,以警告形式呈现),直到你执行 `/supervise resume-auto
132
132
  <task-id>`;你接管期间完成的那一轮会在此时重放给 Decision Worker,因此不会
133
- 丢失已经完成的工作。
133
+ 丢失已经完成的工作。如果你输入后离开,在你最后一次输入且你的那一轮结束之后,Worker
134
+ 持续空闲满 `HUMAN_IDLE_RESUME_MS`(默认 30 分钟)时,自动化会自行恢复(`automation_auto_resumed`);
135
+ 停在半路(例如 Claude 对话框)的轮次只会告警一次。显式的 `/supervise takeover`,
136
+ 以及恢复出来、尚未 `resume-auto` 的任务,仍需 `resume-auto`。
134
137
 
135
138
  **完成后把会话交还给你。** 与其他 transport 不同,任务完成后默认只是让 Pi 与
136
139
  该会话断开,而不是关闭它,方便你在同一窗口中继续工作或查看 Claude 做了什么;
@@ -178,6 +181,18 @@ stream-json` 的方式运行 Claude,完全没有终端界面;一旦设置
178
181
  `git commit -m` 就是数据。除此之外的一切(`for f in …; do echo "$f"`、
179
182
  `rm -rf ./dist`、写入 Claude 自己的 scratchpad)都按配置的策略处理——由
180
183
  Claude 自己的权限模式决定,和你亲自运行 Claude 时一样。
184
+ - 同样始终拒绝的还有**删除底线**:Bash 中的 `rm`/`rmdir`/`unlink`/`shred`/`mv`/
185
+ `rimraf`/`find -delete`/`git clean -C …`/`rsync --delete`——无论出现在语句的哪个
186
+ 位置(因此任何包装器都能看穿),以及引号脚本(`sh -lc '…'`、`trap '…'`)、
187
+ `$(…)`、管道喂给 shell 的文本里的——只能作用于任务目录、额外写入根或临时目录
188
+ 之内。以下会被拒绝:策略无法按字面读出的目标(`$(pwd)/..`、未知的 `$VAR`)、
189
+ 无法解析的 `cd` 之后的相对目标、来自 `xargs` 的目标;任务目录本身或包含它的
190
+ 目录;整个临时/写入根;`.git`、隐藏项通配(`.*`)、后跟 `..` 的通配、不带过滤的
191
+ `find .`;`git prune`、`git gc --prune`、`git reflog expire`。日常清理
192
+ (`rm -rf dist node_modules .cache`、`rm -f src/*.js`、
193
+ `find . -name '*.pyc' -delete`、`d=$(mktemp -d); rm -rf "$d"`、
194
+ `rm -f .git/index.lock`)不受影响。不覆盖:解释器或脚本文件执行的删除、覆盖写
195
+ (`cp`、`ln -sf`、`>`)、非受保护分支上的 `git reset --hard`。
181
196
  - `autonomy.permissionAuthority`(`policy` | `hybrid` 默认 |
182
197
  `decision-worker`)决定谁来回答权限请求——headless 模式下是每一个请求,交互式
183
198
  tmux 模式下只是那些 Claude 本来会弹窗问你的请求:`hybrid` 会让策略独自回答
@@ -326,7 +341,7 @@ commit,通知能说清已验证的 commit 是否在工作树变动之前就已
326
341
  | `HUMAN_WEBHOOK_SECRET` | 未设置 | HMAC 签名密钥;以 `x-pi-supervisor-signature` header 发送 |
327
342
  | `UNATTENDED` | `true` | 任务无需同步人工回调即可运行 |
328
343
  | `REQUIRE_LOCAL_COMMIT` | `true` | 完成前要求在候选所在分支上有本地 commit |
329
- | `MAX_DECISION_RETRIES` | `4`(0–10) | Decision Worker 调用超时或失败(429/529、网络、鉴权)时的重试次数;两次尝试之间依次等待 15s、45s、60s |
344
+ | `MAX_DECISION_RETRIES` | `4`(0–10) | Decision Worker 调用超时,或因等待无法解决的原因失败(prompt 过长、会话损坏)时的重试次数;依次等待 15s、45s、60s。已完成轮次遇到暂时性的 provider/网络故障(429/529、5xx、连接重置)时改为持续等待,每分钟重试一次;凭证被拒、计费问题、模型不存在则立即 park |
330
345
  | `PERMISSION_AUTHORITY` | `hybrid` | `policy` \| `hybrid` \| `decision-worker` |
331
346
  | `REMOTE_AUTHORITY` | `none` | `none` \| `push` \| `pr`;验收通过后开启发布阶段。`--remote` 可按任务覆盖 |
332
347
  | `REMOTE_NAME` | `origin` | 发布授权唯一允许的 remote 名 |
@@ -346,6 +361,7 @@ commit,通知能说清已验证的 commit 是否在工作树变动之前就已
346
361
  | `DEADLINE_GRACE_MS` | `30m` | 自动任务到期后的收尾窗口:空闲的 Worker 会被验收而不是停止;`0` 恢复到期立即停止 |
347
362
  | `DEADLINE_WARNING_MS` | `15m` | 到期前多久提醒并重新询问 Decision Worker;`0` 关闭提醒 |
348
363
  | `NO_OUTPUT_TIMEOUT_MS` | `20m` | Worker 多久没有输出就停止;`0` 关闭该检查 |
364
+ | `HUMAN_IDLE_RESUME_MS` | `30m` | 你在受监督会话中输入后,在你最后一次输入且你的那一轮结束之后 Worker 空闲这么久,即自动恢复自动化(1 分钟到 24 小时);`0` 保持暂停直到 `resume-auto`。不适用于显式接管 |
349
365
  | `EVENT_LOG_MAX_BYTES` | `67108864`(64 MiB) | `events.jsonl` 达到该大小后滚动,保留 5 份滚动文件 |
350
366
 
351
367
  ## 恢复、租约与状态
@@ -474,7 +490,8 @@ npm run test:install
474
490
 
475
491
  详见 [architecture](docs/architecture.md)、[testing](docs/testing.md) 和
476
492
  [releasing](docs/releasing.md);`docs/autonomy-target.md` 记录了本项目所
477
- 围绕的、已确认的无人值守开发目标。
493
+ 围绕的、已确认的无人值守开发目标;[optimization-roadmap](docs/optimization-roadmap.md)
494
+ 是 0.9.2 之后的优化路线图(提案)。
478
495
 
479
496
  ## License
480
497
 
package/README.md CHANGED
@@ -156,7 +156,12 @@ current work and is judged on its next `Stop` instead.
156
156
  pauses (`human_takeover`, visible as a warning) until you run `/supervise
157
157
  resume-auto <task-id>`; the turn that completed while you were driving is
158
158
  replayed to the Decision Worker at that point, so nothing already finished is
159
- lost.
159
+ lost. If you type and walk away, the pause lifts on its own once the Worker has
160
+ sat idle for `HUMAN_IDLE_RESUME_MS` (default 30 minutes) after both your last
161
+ prompt and the end of your last turn (`automation_auto_resumed`); a turn left
162
+ waiting mid-way (a Claude dialog, say) is reported once instead. An explicit
163
+ `/supervise takeover`, and a recovered task until you resume it, waits for
164
+ `resume-auto`.
160
165
 
161
166
  **Completion hands the session back.** Unlike other transports, a completed
162
167
  task by default disconnects Pi from the session instead of closing it, so you
@@ -214,6 +219,21 @@ Supervisor being able to see it, or when you don't need to attach.
214
219
  `git commit -m` stores it. Everything else (`for f in …; do echo "$f"`,
215
220
  `rm -rf ./dist`, a Write to Claude's own scratchpad) follows the configured
216
221
  policy — Claude's own permission mode governs it, as when you run Claude.
222
+ - Also always denied: the **delete floor**. A Bash `rm`/`rmdir`/`unlink`/
223
+ `shred`/`mv`/`rimraf`/`find -delete`/`git clean -C …`/`rsync --delete` —
224
+ wherever it stands in the statement, so behind any wrapper, and inside
225
+ quoted scripts (`sh -lc '…'`, `trap '…'`), `$(…)` and text piped into a
226
+ shell — may only reach paths inside the task directory, its extra write
227
+ roots or the temp directory. Refused: a target the policy cannot read
228
+ literally (`$(pwd)/..`, an unknown `$VAR`), one after an unresolvable `cd`,
229
+ one fed by `xargs`; the task directory itself or anything containing it; a
230
+ whole temp/write root; `.git`, hidden-entry globs (`.*`), globs followed by
231
+ `..`, an unfiltered `find .`; `git prune`, `git gc --prune`,
232
+ `git reflog expire`. Ordinary cleanup (`rm -rf dist node_modules .cache`,
233
+ `rm -f src/*.js`, `find . -name '*.pyc' -delete`, `d=$(mktemp -d); rm -rf
234
+ "$d"`, `rm -f .git/index.lock`) is untouched. Not covered: deletes run by an
235
+ interpreter or script file, overwrites (`cp`, `ln -sf`, `>`), and
236
+ `git reset --hard` outside protected branches.
217
237
  - `autonomy.permissionAuthority` (`policy` | `hybrid` default |
218
238
  `decision-worker`) controls who answers a permission request — every
219
239
  request in headless mode, and in interactive tmux mode only those Claude
@@ -418,7 +438,7 @@ unparsable keeps the default and is reported once as a
418
438
  | `HUMAN_WEBHOOK_SECRET` | unset | HMAC signing secret; sent as the `x-pi-supervisor-signature` header |
419
439
  | `UNATTENDED` | `true` | Task runs without a synchronous human callback |
420
440
  | `REQUIRE_LOCAL_COMMIT` | `true` | Require a local commit on the candidate's branch before completion |
421
- | `MAX_DECISION_RETRIES` | `4` (0–10) | Retries of a Decision Worker call that times out or fails (429/529, network, auth); waits 15s, 45s, then 60s between attempts |
441
+ | `MAX_DECISION_RETRIES` | `4` (0–10) | Retries of a Decision Worker call that times out or fails for a reason waiting does not fix (a prompt that is too long, a corrupted session); waits 15s, 45s, then 60s. A transient provider or network outage (429/529, 5xx, resets) on a completed turn is waited out instead, one attempt a minute; rejected credentials, billing and a missing model park at once |
422
442
  | `PERMISSION_AUTHORITY` | `hybrid` | `policy` \| `hybrid` \| `decision-worker` |
423
443
  | `REMOTE_AUTHORITY` | `none` | `none` \| `push` \| `pr`; grants the publish phase after verification passes. `--remote` overrides it per task |
424
444
  | `REMOTE_NAME` | `origin` | The single remote a publish grant may name |
@@ -438,6 +458,7 @@ unparsable keeps the default and is reported once as a
438
458
  | `DEADLINE_GRACE_MS` | `30m` | Close-out window after the deadline for automatic tasks: an idle Worker is verified instead of stopped; `0` restores the immediate stop |
439
459
  | `DEADLINE_WARNING_MS` | `15m` | How long before the deadline the Decision Worker is warned and re-asked; `0` disables the warning |
440
460
  | `NO_OUTPUT_TIMEOUT_MS` | `20m` | Stop a Worker that has produced no output for this long; `0` disables the check |
461
+ | `HUMAN_IDLE_RESUME_MS` | `30m` | After you type into a supervised session, resume automation once the Worker has been idle this long after your last prompt and the end of your last turn (1 minute to 24 hours); `0` keeps the pause until `resume-auto`. Never applies to an explicit takeover |
441
462
  | `EVENT_LOG_MAX_BYTES` | `67108864` (64 MiB) | Rotates `events.jsonl` at this size; 5 rotated files are kept |
442
463
 
443
464
  ## Recovery, leases and state
@@ -588,7 +609,8 @@ from a path that is not group/world-writable.
588
609
  See [architecture](docs/architecture.md), [testing](docs/testing.md), and
589
610
  [releasing](docs/releasing.md) for more detail; `docs/autonomy-target.md`
590
611
  records the confirmed unattended-development target this project is built
591
- around.
612
+ around, and [`docs/optimization-roadmap.md`](docs/optimization-roadmap.md) (in
613
+ Chinese) proposes the post-0.9.2 optimization roadmap.
592
614
 
593
615
  ## License
594
616
 
@@ -138,13 +138,44 @@ An owned manual worker gets a private tmux server/socket and executes the
138
138
  validated Claude command directly in the pane. An owned automatic worker instead
139
139
  starts the Supervisor bridge through a cgroup-joining pane bootstrap, so its
140
140
  bridge identity is not a manual adoption target. The worker environment is
141
- passed through unchanged (apart from removing `CLAUDECODE` so nested Claude can
142
- start); credentials are not copied into a file, and credential-shaped command
143
- arguments are still rejected.
141
+ passed through unchanged, apart from removing `CLAUDECODE` so nested Claude can
142
+ start and defaulting `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1` (see the hook
143
+ relay below); credentials are not copied into a file, and credential-shaped
144
+ command arguments are still rejected.
144
145
  `load-buffer`, bracketed `paste-buffer` and `send-keys Enter` provide the input
145
146
  boundary without interpolating a task into a shell command. C0/C1 terminal
146
147
  control bytes are neutralized (escape sequences removed, a lone CR becomes a newline,
147
- other C0/C1 bytes become spaces); CRLF is normalized to a newline. Automatic agents,
148
+ other C0/C1 bytes become spaces); CRLF is normalized to a newline.
149
+
150
+ A send first waits up to `inputReadyTimeoutMs` (30 s) for an idle prompt; a
151
+ prompt still busy, showing a banner or leftover input after that is refused
152
+ with a retryable `WorkerInputError` (`src/worker/input-error.ts`), since
153
+ nothing was typed. The wait also yields — retryable, nothing typed — as soon
154
+ as another turn starts (a human prompt at the attached pane, Claude's own
155
+ background completion, a turn ending), so a message decided for an earlier
156
+ state is never pasted over it. Just before the paste and before Enter, every
157
+ tmux mode is left (`copy-mode -q`; copy mode swallows Enter and can be stacked
158
+ on the tree chooser). An interactive send is then confirmed by its
159
+ `UserPromptSubmit` hook: if none arrives within `inputConfirmTimeoutMs` (8 s)
160
+ and the input box visibly still holds the message (or Claude's long-paste
161
+ placeholder), Enter is sent again, at most twice — never blindly, since Enter
162
+ on a dialog would pick its default. A message still stuck after that is a
163
+ non-retryable error (a resend would duplicate it); an empty box without the
164
+ hook is accepted and logged, leaving a broken hook channel to the no-output
165
+ watchdog. An `idle_prompt` notification arriving while a send is still
166
+ confirming neither counts as delivery nor closes the turn that is only
167
+ starting. The Supervisor re-applies a Decision `continue`/`redirect`/`answer`/
168
+ `retry` whose message was refused as retryable after 15 s, doubling to at most
169
+ a minute, up to five times (`worker_input_deferred`); any fresh Worker event
170
+ supersedes the retry, as do a later turn, an operator's own message or a
171
+ state change, and only then is the task parked, as a Worker input failure
172
+ (`worker_input_failed`) rather than a Decision Worker failure. An owned
173
+ interactive session turns prompt suggestions off, both in its settings
174
+ (`promptSuggestionEnabled`) and through `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION`,
175
+ which Claude reads first: the suggestion's ghost text reads as leftover input.
176
+ An adopted session keeps the user's own setting.
177
+
178
+ Automatic agents,
148
179
  background tasks, plugins, MCP servers and nested Claude processes stay in the
149
180
  same cgroup and are cleaned with the Worker; they are intentionally not rejected
150
181
  or polled as a nested-process policy failure. The lexical Bash/file-tool policy
@@ -204,10 +235,25 @@ are mutually exclusive sub-modes of the automatic tmux transport.
204
235
  are configured to run a small embedded relay script
205
236
  (`src/hooks/relay.ts`'s `HOOK_RELAY_SCRIPT`, written to
206
237
  `<stateDir>/hooks/relay.js`). The relay reads the hook event JSON from stdin,
207
- hashes the event's `cwd` to find `<stateDir>/hooks/by-cwd/<sha256(cwd)>` — a
238
+ hashes the task directory to find `<stateDir>/hooks/by-cwd/<sha256(cwd)>` — a
208
239
  symlink to a `HookServer`'s unix socket, created only while a Supervisor holds
209
- that cwd — and forwards the event over the socket, printing the reply as
210
- Claude's hook output. A cwd with no owning Supervisor is a fast no-op: the
240
+ that cwd — and forwards the event over the socket together with the directory
241
+ it routed by (`routeCwd`), printing the reply as Claude's hook output. The task
242
+ directory is Claude's `CLAUDE_PROJECT_DIR` (set on every hook command to the
243
+ session's launch directory) when a Supervisor owns it, else the event's `cwd`:
244
+ a Bash `cd` into a subdirectory moves `cwd` but not the project directory, so
245
+ routing by `cwd` alone would silently drop every later event of that Worker.
246
+ A nested Claude launched in a different directory reports that directory as
247
+ its project directory and is not routed to the parent's Supervisor (one
248
+ launched in the task directory itself is, as it always was). A Bash request
249
+ whose shell cwd differs from the task directory is presented to the policy as
250
+ `cd <shell cwd> && <command>`: the policy does not track `cd`, but such a
251
+ command is never classed as routine, so under `hybrid` authority the Decision
252
+ Worker judges it with its real directory shown. Automatic Workers also get
253
+ `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1` (unless the caller set it), which
254
+ returns Claude's Bash to the task directory after each command, so the
255
+ policy's relative-path resolution against the task directory stays true. A
256
+ directory with no owning Supervisor is a fast no-op: the
211
257
  relay `stat`s the symlink path and returns before touching `net`. Non-blocking
212
258
  events (`SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Notification`) are
213
259
  fire-and-forget; `PreToolUse`, `PermissionRequest` and `Stop` block Claude for
@@ -248,7 +294,21 @@ Worker event; the Supervisor appends a bounded `human_input` event, enters
248
294
  human takeover (`human_takeover` with `data.source: "worker_prompt"`) if not
249
295
  already active, and pauses Decision Worker notification of further
250
296
  `turn_completed` events (they are still recorded so `resume-auto` can replay
251
- the last one) until `/supervise resume-auto`. A `pre`-phase request is still
297
+ the last one) until `/supervise resume-auto`. That pause (gate `worker_prompt`)
298
+ also lifts on its own once the Worker has sat idle for `humanIdleResumeMs`
299
+ (`PI_CLAUDE_SUPERVISOR_HUMAN_IDLE_RESUME_MS`, default 30 minutes) after both
300
+ the human's last prompt and the end of their last turn: every further prompt
301
+ and every turn end restart the clock, a running turn defers it (one silent for
302
+ a whole period, typically a Claude dialog left open, is reported once as
303
+ `human_session_stalled` with an outbound notice), and `automation_auto_resumed`
304
+ replays the last completed turn like `resume-auto`. A failed status read
305
+ re-arms the check rather than ending it. An explicit takeover, and a recovered
306
+ task until resumed, use gate `other` and never resume on their own. Claude can
307
+ submit a pasted message in another shape than it was pasted (a long paste as a
308
+ placeholder); a prompt that arrives while a paste of the Supervisor's own is
309
+ still confirming is taken as that paste, not as a human; after that only the
310
+ exact text matches, so a later human prompt still reads as human. A
311
+ `pre`-phase request is still
252
312
  answered automatically during a human takeover (policy deny or defer) since it
253
313
  is not something a human is expected to approve; only a `prompt`-phase request
254
314
  pends for `/supervise approve`.
@@ -516,14 +576,73 @@ approval for ordinary actions; a task that cannot safely produce a candidate is
516
576
  parked or failed without granting remote/main authority. Model/API failures are
517
577
  detected from the Pi `stopReason` (a provider error resolves the prompt normally
518
578
  rather than throwing); if the Decision Worker
519
- API/model call fails, the system records `decision_worker_failed`, applies the
520
- bounded retry/park policy and preserves the candidate evidence. An abort is never
579
+ API/model call fails, the system classifies the error. Rejected, missing or
580
+ expired credentials, billing and a missing model are configuration errors and
581
+ park at once ("Decision Worker configuration failed"). An explicitly listed
582
+ transient provider or network failure (429/529 overloads and rate limits,
583
+ including a quota that resets, 5xx, gateway, connection and transport timeout
584
+ errors) on a completed turn is waited out, backing off to one attempt a minute
585
+ and recording each `decision_retry`: the task has no human to resume it, and if
586
+ no decision lands the idle watchdog verifies the Worker's finished work — the
587
+ close-out verifies at once rather than wait on a decision that is backing off.
588
+ Everything else stays bounded by `maxDecisionRetries` and then parks: a prompt
589
+ that is too long, a corrupted session, the Decision Worker's own request
590
+ timeout; a permission request, which blocks the Worker mid-turn; and an exit,
591
+ after which only this decision starts verification. A completed turn that a
592
+ later event supersedes while its decision is failing is dropped
593
+ (`decision_ignored`) rather than retried or parked, so the current turn is
594
+ decided next. A
595
+ decision that lands for a turn a later turn, message or verification has since
596
+ superseded is recorded as `decision_ignored` rather than applied. An error
597
+ thrown while applying a decided action is recorded as `decision_action_failed`,
598
+ not as a model failure. The startup
599
+ instructions prompt retries transient provider errors on the same backoff within `maxDecisionRetries`, so a
600
+ provider overload at start does not fail the task before its first turn. An abort is never
521
601
  retried, and a `noop` reply on a completed turn or a permission request parks the
522
602
  candidate rather than being treated as a resolved decision, while a `noop` on a
523
- clean Worker exit proceeds to verification. Optional alert
603
+ clean Worker exit proceeds to verification. A `stop` on a completed turn of an
604
+ unattended task without remote authority stops the Worker (never keeping it open)
605
+ and then verifies its finished work instead of discarding it (`decision_overridden`);
606
+ no repair round may follow, so a failure blocks the candidate. A `stop` on a
607
+ pending permission, on a turn the Worker has already resumed, or on a task with
608
+ remote authority stays a plain stop. Optional alert
524
609
  delivery remains independent from event-log persistence, but notification is not
525
610
  the control boundary.
526
611
 
612
+ Beneath every authority sits the delete floor (`deleteFloorViolation` in
613
+ `src/policy.ts`, applied inside `evaluatePermission` to every Bash request, so no
614
+ authority mode, human `/supervise approve` included, can lift it). It reads
615
+ fail-closed rather than modelling each wrapper: any word of a statement that
616
+ names `rm`, `rmdir`, `unlink`, `shred`, `mv` or `rimraf` counts wherever it
617
+ stands (so `sudo`, `busybox`, `env -i`, `command -p`, function and `case` bodies
618
+ are seen through), as do `find -delete`/`-exec rm`, `git clean -C`, `rsync
619
+ --delete` and `git worktree remove --force`. Only text commands (`echo`,
620
+ `grep`, `cat`, …), interpreters, and tools whose `rm` subcommand is not a file
621
+ delete (`git rm`, `npm rm`, `docker rm`) end the scan. Quoted arguments that
622
+ mention a delete (`sh -lc '…'`, `watch '…'`, `trap '…'`), text piped into a
623
+ shell, unquoted heredocs, and `$(…)`/backtick/`<(…)` bodies are judged as
624
+ scripts; beyond six levels of nesting a delete is refused outright. A target is
625
+ refused when it is dynamic, follows a `cd` the reading cannot resolve, or comes
626
+ from `xargs`/`parallel`; when it resolves (parent through realpath, last name as
627
+ written, so `rm link` stays a link removal) outside the task directory, the
628
+ extra write roots and the temp directory; when it is the task directory itself,
629
+ a directory that contains the task, a whole write/temp root or a glob across its
630
+ top; when it is a glob over hidden entries, a glob followed by `..`, or a `find`
631
+ over the task tree whose filter could reach `.git` (a `!`/`-not`/`-o` branch, or
632
+ a pattern matching `.git` or its entries); and when it is Git's own store (a
633
+ stale `.git/*.lock` excepted). Brace alternatives are expanded and judged one by
634
+ one. `git prune`, `git gc --prune*` and `git reflog expire/delete` are refused
635
+ because with no remote authority the local commits are the only copy. Literal
636
+ globs (`rm -rf dist/*`), names bound to literal text, to `$(mktemp …)` or to a
637
+ `for f in *.tmp` glob, and `$HOME`/`$TMPDIR`/`$PWD` are judged as the paths they
638
+ spell. Outside what this floor reads: interpreters and script files
639
+ (`python -c "shutil.rmtree(...)"`, `bash ./cleanup.sh`), overwrites (`cp`,
640
+ `ln -sf`, `install`, `truncate`, `tar -C`, `>`), history rewrites such as
641
+ `git reset --hard` or `git stash clear` (the protected-branch rules cover only
642
+ protected branches), and a sibling task under the same temp directory. The
643
+ cgroup boundary limits resources, not paths; an operator who needs those closed
644
+ runs the Worker in a container or OS sandbox with a read-only view of the rest.
645
+
527
646
  Permission requests are not all routed to the Decision Worker model. `autonomy.permissionAuthority`
528
647
  (`policy` | `hybrid`, default | `decision-worker`) chooses the authority: `policy` answers
529
648
  every request from the deterministic policy alone, `decision-worker` sends every
@@ -613,10 +732,17 @@ park it without requiring a human to be online. The Reviewer retries provider
613
732
  errors with a fresh session within a total review budget
614
733
  (`PI_CLAUDE_SUPERVISOR_REVIEW_TIMEOUT_MS`, default 10 minutes). Truncated
615
734
  (oversize) evidence requests a bounded repair before parking, while incomplete
616
- evidence still parks.
735
+ evidence (an unsafe path, a read or git failure, an untracked hard link, which
736
+ could hide text from the Reviewer) still parks. An untracked binary (with its
737
+ size), a symlink (with its target, not followed) or a special file is named in
738
+ the evidence without its content; that is complete evidence, not a park.
617
739
 
618
740
  A `revise` result produces an audited repair round and sends a bounded corrective
619
741
  instruction to a still-live `repairableSession` Worker. Checks and review then run again.
742
+ The Decision Worker sees the last result tagged with the Worker turn it judged, and chooses
743
+ when to verify again; if it keeps steering instead, the Supervisor verifies on its own once
744
+ the Worker has taken three turns since that failure (`decision_overridden`), so a Decision
745
+ Worker reasoning from the stale failure cannot hold a fixed Worker in a loop until the deadline.
620
746
  The repair budget defaults to three rounds. P0/P1 findings block a `pass` but are repair
621
747
  inputs like any other concrete finding (a `pass` carrying one is treated as `revise`); a
622
748
  `human` verdict, repeated findings or an exhausted budget stop automation and park a
@@ -119,7 +119,8 @@ control. They are not entered by ordinary uncertainty, and a legacy approval obj
119
119
  the deterministic known-command remote push/main merge denial. The existing independent Review
120
120
  and protected CI/release paths remain the final external checks. Automatic Claude workers preserve
121
121
  Claude Code's normal environment, network, tool, agent and MCP surface; `CLAUDECODE` is removed
122
- only to permit intentional nested Claude sessions. The Supervisor-owned cgroup remains a cleanup
122
+ only to permit intentional nested Claude sessions, and `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`
123
+ is defaulted so Bash returns to the task directory after each command. The Supervisor-owned cgroup remains a cleanup
123
124
  boundary, not a capability allowlist. Automatic tmux parent-death recovery leaves an empty
124
125
  cgroup as evidence and permits `recover --takeover` only after Supervisor ownership, persisted
125
126
  Worker/cgroup identity (including the cgroup device/inode), dead tmux-server identity, a gone
@@ -33,7 +33,8 @@ custom/nested descendants are trusted rather than denied by a nested-process gua
33
33
  - Manual Worker and verifier processes retain the baseline environment behavior;
34
34
  automatic Workers pass the full Supervisor environment, including credentials,
35
35
  helpers, custom settings and proxy/network variables, with only `CLAUDECODE`
36
- removed so nested Claude can start.
36
+ removed so nested Claude can start and `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`
37
+ defaulted so Bash stays in the task directory.
37
38
  - Startup failures clean up a worker and do not let event-log failures hide the
38
39
  original error.
39
40
  - Default wall-clock and no-output watchdogs stop stalled workers.