Skip to content

Commit 051a6f5

Browse files
authored
Merge pull request #39 from vitry/fix/rescue-worktree-late-binding
fix: bind Rescue to linked worktrees
2 parents f07eb81 + 9e6155d commit 051a6f5

44 files changed

Lines changed: 6923 additions & 545 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All notable changes follow Semantic Versioning.
44

55
## Unreleased
66

7+
- Added compatible Rescue worktree late binding: lifecycle proof retains the origin workspace while the first trusted prepare automatically and immutably binds one execution workspace from the same canonical Git common-dir, without manual handoff. Role preview and children cannot claim; unrelated repositories fail closed; Root Stop, a new prompt, and SessionEnd revoke or replace authority before target cleanup.
78
- Accepted ZCode CLI 0.16.3's captured initial empty-session revision and pre-turn settings snapshots while retaining exact empty-state, event-sequence, identity, workspace, and activity checks. Real qualification now proves two visible responses through each turn's exact persisted user-root parent chain when the CLI remaps request input IDs.
89
- Removed the plugin-defined ordinary Rescue completion deadline while retaining finite request, review-gate, qualification, caller credential, and one-shot preparation budgets. Active parent authority is now hook-lifecycle-bound; same-parent-turn continuation replaces consumed preparation generation 1 with an executor-bound generation 2, follows up the exact stopped child, and reuses the exact binding and ZCode session. Root Stop, replacement prompts, SessionEnd, explicit cancellation, SIGINT, and SIGTERM remain authoritative boundaries. Role readiness now distinguishes `caller-unavailable` and `inspection-unavailable` from managed setup states; existing owned Roles require the normal one-time setup upgrade.
910
- Added private durable per-job human-readable logs that retain the complete accepted safe semantic-progress history while job previews remain bounded to four entries. Exact-owner detailed status displays the private absolute path; compact, foreign, sibling-session, sidecar, relay, and terminal surfaces remain path-free, with no new log command or retention lifecycle.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ A source checkout and an installed plugin use intentionally isolated namespaces:
4848

4949
On every owned parent turn, the owned `UserPromptSubmit` hook injects one machine-rendered, instance-bound launcher command derived from the exact plugin instance that executed the hook. Root and its Rescue child reuse those exact bytes and append only fixed Rescue arguments. They never construct a path from cwd or Skill prose, never call the direct companion form `node scripts/zcode-companion.mjs`, and never use PATH, a global package, or a cache search to select another plugin instance. This removes model-authored path selection without weakening instance or namespace isolation.
5050

51+
Rescue distinguishes the conversation's origin workspace from its execution workspace. When Root creates or enters a linked worktree during the same parent turn, the first trusted `prepare rescue` automatically binds execution there; no manual handoff is needed. The origin itself or a canonical linked-worktree top level is eligible only when it shares the same canonical Git common-dir. That target is immutable for the turn, so another worktree or an unrelated repository is rejected. Role inspection is read-only and a child cannot claim or change the target. Root `Stop`, a new prompt, and `SessionEnd` revoke or replace authority before cleanup across the origin and bound target.
52+
5153
`source-session-unproven` is terminal for that Rescue route: use the launcher from the active owned lifecycle context, but do not run `$zcode:setup`, prepare, follow up, or spawn from the unproven source checkout. A launcher error caused by a shell-unsafe install path is also terminal and provides a fixed reinstall remedy; reinstall the plugin to a shell-safe path and retry from a new owned parent turn. Neither condition authorizes a fallback launcher or automatic redirect.
5254

5355
Rescue has two equivalent entry forms. An explicit `$zcode:rescue` request is literal and applicable; Root may also choose Rescue proactively from the complete business objective. This is automatic routing and there is no `--auto` option. Explicit `--fresh` or `--resume` remains authoritative; clear proactive continuations materialize resume and clear independent work materializes fresh before any child starts.

README.zh-CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ source checkout 和已安装插件使用刻意隔离的命名空间:source dev
4848

4949
在每个受管父 turn 中,受管 `UserPromptSubmit` hook 都会注入一条由执行该 hook 的精确插件实例机器渲染的 instance-bound launcher command。Root 和 Rescue 子 agent 原样复用这些精确字节,并且只追加固定 Rescue 参数。它们绝不从 cwd 或 Skill 文本构造路径,绝不调用直接 companion 形式 `node scripts/zcode-companion.mjs`,也绝不通过 PATH、全局包或 cache 搜索选择另一个插件实例。这样无需模型自行选择路径,同时不削弱实例或命名空间隔离。
5050

51+
Rescue 会区分对话的 origin workspace 与 execution workspace。Root 在同一个 parent turn 中创建或进入 linked worktree 时,第一次可信的 `prepare rescue` 会自动绑定到该 execution workspace,不需要手动 handoff。只有 origin 本身,或与它共享相同的 canonical Git common-dir 的 canonical linked-worktree 顶层目录才合格。目标在同一 turn 内不可变,因此其他 worktree 或无关仓库会被拒绝。Role inspection 只读,child 不能 claim 或更改目标。Root `Stop`、新 prompt 与 `SessionEnd` 会先撤销或替换 origin 和已绑定目标之间的授权,再执行清理。
52+
5153
`source-session-unproven` 对该 Rescue 路由是终态:应使用活动受管 lifecycle context 中的 launcher,但不要从未证明的 source checkout 运行 `$zcode:setup`、prepare、follow up 或 spawn。launcher error 由 shell-unsafe 安装路径触发时同样是终态,并给出固定的重新安装 remedy;请把插件重新安装到 shell-safe 路径,再从新的受管父 turn 重试。两种情况都不授权 fallback launcher 或自动重定向。
5254

5355
Rescue 有两种等价入口:显式 `$zcode:rescue` 是请求中字面且适用的入口;Root 也可以根据完整业务目标主动选择 Rescue。这就是自动路由,不提供 `--auto` 选项。显式 `--fresh``--resume` 始终权威;明确的主动续做会在 child 启动前物化为 resume,明确的独立工作会物化为 fresh。

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Report suspected vulnerabilities privately through GitHub's private vulnerabilit
1616
- A private durable per-job log contains every accepted safe semantic progress event successfully dispatched by that bounded allowlist and may also contain current-turn visible assistant text selected by the exact existing linkage rules and authoritative final output. Raw command stdout/stderr, arbitrary tool payloads (input/output/errors/metadata), raw reasoning, file or patch contents, environment values, credentials, capabilities, and hidden messages are never directly ingested as log source fields. The log is not a semantic secret-redaction boundary: if visible assistant or final text itself quotes or paraphrases sensitive material, that selected text is retained. Keep secrets out of visible model text and protect the private log accordingly. The log is observational and cannot establish or alter terminal authority. Its absolute path is disclosed only by exact-owner detailed status, never by compact or foreign projections, sibling sessions, sidecars, relays, or terminal notices.
1717
- Child stderr and detailed progress stay in the child thread. Parent-visible output is limited to host lifecycle events and the final public result. Subscription or optional progress-sink failure is observational and cannot weaken the authoritative completion guard.
1818
- Rescue task material exists in the routing rollout only as the Root parent's single LF-terminated JSON line sent through `write_stdin` to `prepare rescue`. The companion requires a raw-capable TTY and enables raw mode before emitting task-free readiness and before accepting any task bytes; readiness is nonterminal. Root sends no EOF or U+0004. Non-TTY/readiness/raw-mode failure stops before delivery. Tool output must never contain or echo the payload. Prepared state is bound to the exact Codex session, initiating turn, canonical workspace, and executor identity; it is single consume, has a bounded expiry, and is subject to private-state cleanup. The task, source, and options must never appear in argv, environment variables, output, logs, artifacts, relays, status, task names, or any child assignment/transcript. Named and generic children are task-blind and capability-free and receive only the constant `invoke-prepared rescue` assignment.
19+
- Rescue records the trusted prompt cwd as the origin workspace and lets only the first trusted prepare bind one execution workspace. Cross-worktree binding requires an exact canonical Git top level with the same canonical Git common-dir; the first prepare makes that target immutable for the turn. Role preview is read-only, a child cannot claim authority, and an unrelated repository fails closed. Root Stop, a new prompt, and SessionEnd revoke or replace authority before target cleanup, while all preparation, executor, job, binding, broker, and peer state remains scoped to the execution workspace.
1920
- A durable Rescue binding authorizes the same stopped child to continue only its exact ZCode session. The private `anchorJobId` and `currentJobId` remain inside protected plugin state and never cross the parent-to-child message boundary. Missing, closed, corrupt, permission-incompatible, workspace-mismatched, executor-mismatched, or provenance-inconsistent bindings fail closed.
2021
- Ordinary foreground completion has no plugin wall-clock deadline. Authority still ends at Root `Stop`, replacement prompt, `SessionEnd`, explicit `$zcode:cancel`, `SIGINT`, or `SIGTERM`; request, review-gate, qualification, caller credential, and one-shot preparation budgets remain finite. Same-parent-turn continuation requires the exact stopped executor, exact binding operation/anchor/current CAS values, and exact ZCode session. Each 30-minute preparation generation is single-consume and generation 2 is bound to generation 1's exact executor.
2122
- Role readiness is fail-closed but distinguishes unavailable caller authority (`caller-unavailable`) from an unavailable inspection channel (`inspection-unavailable`) and from managed install/upgrade/drift/conflict/restart/genuine-unsupported states. Only the managed states authorize setup guidance; owned prior Role bytes require the normal one-time upgrade and foreign Role state is never adopted.

docs/adr/0010-use-thread-bound-direct-companion.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
status: accepted
33
supersedes: caller-capability-through-model-and-fd
4-
amended: 2026-08-20
4+
amended: 2026-08-20; 2026-08-21
55
---
66

77
# Use a thread-bound direct companion for installed Skills
@@ -82,6 +82,12 @@ binding child identity through native lifecycle hooks, and failing closed is
8282
retained. Only Rescue command-location selection is superseded: the model no
8383
longer constructs or directly invokes `scripts/zcode-companion.mjs`.
8484

85+
## Rescue worktree late-binding amendment (2026-08-21)
86+
87+
The active parent turn now distinguishes its trusted origin workspace from one optional execution workspace. The first trusted prepare automatically binds an immutable target for the turn, without manual handoff. A different target is eligible only when it is an exact canonical linked-worktree top level with the same canonical Git common-dir as the origin workspace. Role inspection remains read-only and a child cannot claim or redirect this authority.
88+
89+
SubagentStart and SubagentStop may continue to arrive at the origin workspace. A generation-bound private route points to executor storage in the execution workspace, where preparation, job, binding, broker, and peer state remain isolated. Root Stop, a new prompt, and SessionEnd revoke or replace authority before advisory cleanup. This deepens the existing thread-bound active-turn semantic; it does not create a second handoff authority or weaken the launcher boundary.
90+
8591
## Rejected alternative
8692

8793
A bundled stdio MCP server would provide structured arguments, and current

docs/adr/0013-bind-rescue-child-to-zcode-session.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ An active exact child is rejoined without preparation or invocation. A stopped e
1515

1616
Legacy jobs-only state may be adopted only when it supplies one exact eligible candidate and no conflicting pending state. A permission change prevents resume and requires an explicit fresh operation to capture the current permission snapshot. `SessionEnd` closes the ending Codex session's whole binding partition. Missing, invalid, closed, corrupt, ambiguous, wrong-workspace, wrong-executor, or provenance-mismatched state must fail closed without selecting a latest session or another child.
1717

18+
For linked worktrees, the prompt-proved origin workspace and the Rescue execution workspace are distinct. The first trusted prepare automatically binds one immutable execution target for the turn, without manual handoff, and only an exact canonical linked worktree sharing the same canonical Git common-dir is eligible. A child cannot claim the target. The stopped child's generation-bound route and all durable binding state remain in that execution workspace; Root Stop, a new prompt, and SessionEnd revoke or replace the origin authority before cleaning the target.
19+
1820
This replaces ADR 0010 only where that decision treated a stopped child as reusable solely for the immediate `needs-choice` exchange. Choice continuation remains same-child; this decision also permits a later prepared turn for the exact durably bound operation.

0 commit comments

Comments
 (0)