Skip to content

Commit 5654245

Browse files
feat(droid-control): decouple tctl launch --cwd from --repo-root + degraded-tail recipe (MB-18)
--cwd now sets the child working directory independently on both backends (defaulting to --repo-root when unset) and is recorded in launch provenance, replacing the sh -c 'cd ... && exec' shim. Documents the degraded-tail repro recipe (slow PreToolUse hook in a scratch project) in the droid-control skill. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 6533b9f commit 5654245

5 files changed

Lines changed: 62 additions & 15 deletions

File tree

plugins/droid-control/ARCHITECTURE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Terminal workflows use `bin/tctl` as the only launch/control boundary. It hides
117117
| `tuistory` | Virtual PTY sessions, deterministic waits/snapshots, asciinema recording at launch. | Fast TUI automation and most demo captures. |
118118
| `true-input` | Headless Wayland compositor, real terminal emulator, native key injection, PTY log/screenshot/video capture. | Real terminal rendering or keyboard-encoding proof. |
119119

120-
`tctl` also enforces Droid CLI launch invariants. `droid-dev` sessions must provide `--repo-root`, which lets `tctl` set `DROID_DEV_REPO_ROOT` and record provenance for the captured branch and commit.
120+
`tctl` also enforces Droid CLI launch invariants. `droid-dev` sessions must provide `--repo-root`, which lets `tctl` set `DROID_DEV_REPO_ROOT` and record provenance for the captured branch and commit. `--cwd` is independent: it sets the child's working directory (recorded in provenance) and defaults to `--repo-root` when unset, so a session can run one worktree's code from a different project directory.
121121

122122
Browser/Electron and native-desktop workflows intentionally do **not** go through `tctl`. They have their own control boundaries: `agent-browser`'s persistent Playwright daemon for DOM snapshots, screenshots, and CDP-connected apps; `cua-driver`'s daemon for accessibility trees and per-`(pid, window_id)` element caches on desktop GUIs.
123123

plugins/droid-control/bin/tctl

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ Launch options:
3131
--terminal <name> Override terminal for true-input
3232
--cols <n> Columns (default: 120)
3333
--rows <n> Rows (default: 36)
34-
--cwd <path> Working directory (tuistory)
34+
--cwd <path> Working directory for the child, both backends
35+
(default: --repo-root when set)
3536
--repo-root <path> Git worktree root for provenance + droid-dev launches
3637
--env <KEY=VALUE> Environment variable (repeatable)
3738
--tmux Wrap the command in tmux with RGB-safe tuistory defaults
@@ -319,21 +320,25 @@ require_git_worktree_root() {
319320
write_provenance() {
320321
local session="$1"
321322
local repo_root="$2"
323+
local cwd="$3"
322324
local file branch commit
323325
file="$(provenance_file "$session")"
324326

325-
if [[ -z "$repo_root" ]]; then
327+
if [[ -z "$repo_root" && -z "$cwd" ]]; then
326328
rm -f "$file"
327329
return 0
328330
fi
329331

330-
branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown')"
331-
commit="$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || printf 'unknown')"
332-
cat > "$file" <<PROVENANCE
333-
repo_root=$repo_root
334-
branch=$branch
335-
commit=$commit
336-
PROVENANCE
332+
{
333+
if [[ -n "$repo_root" ]]; then
334+
branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown')"
335+
commit="$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || printf 'unknown')"
336+
printf 'repo_root=%s\nbranch=%s\ncommit=%s\n' "$repo_root" "$branch" "$commit"
337+
fi
338+
if [[ -n "$cwd" ]]; then
339+
printf 'cwd=%s\n' "$cwd"
340+
fi
341+
} > "$file"
337342
}
338343

339344
resolve_backend() {
@@ -937,9 +942,12 @@ cmd_launch() {
937942

938943
if [[ -n "$repo_root" ]]; then
939944
require_git_worktree_root "$repo_root"
940-
if [[ -n "$cwd" && "$cwd" != "$repo_root" ]]; then
941-
die "--cwd must match --repo-root when --repo-root is set"
942-
fi
945+
fi
946+
947+
if [[ -n "$cwd" ]]; then
948+
[[ "$cwd" = /* ]] || die "--cwd must be an absolute path: $cwd"
949+
[[ -d "$cwd" ]] || die "--cwd does not exist: $cwd"
950+
elif [[ -n "$repo_root" ]]; then
943951
cwd="$repo_root"
944952
fi
945953

@@ -977,7 +985,7 @@ cmd_launch() {
977985
WARMED_UP="0"
978986
REPO_ROOT="$repo_root"
979987
save_session_state "$session"
980-
write_provenance "$session" "$repo_root"
988+
write_provenance "$session" "$repo_root" "$cwd"
981989

982990
if [[ "$BACKEND" == "tuistory" ]]; then
983991
launch_tuistory "$session" "$cols" "$rows" "$record_path"

plugins/droid-control/skills/droid-cli/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ $TCTL -s demo press tab
7272
- **No per-branch builds.** One `npm run setup` (in any checkout) installs the shim. Switching branches is instant via `--repo-root`.
7373
- **Prerequisite:** The target worktree must have `node_modules` installed (`npm install` at the repo root). If missing, the bun launch fails.
7474
- **`tctl --repo-root`** sets `DROID_DEV_REPO_ROOT` automatically and pins the session to that worktree.
75+
- **`tctl --cwd`** is independent of `--repo-root`: run droid against one worktree's code while sitting in a different project directory (e.g., a scratch project with its own `.factory/settings.json` hooks). Defaults to `--repo-root` when unset.
7576

7677
### `droid-dev`
7778

plugins/droid-control/skills/droid-control/SKILL.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,44 @@ Terminal drivers use the unified `tctl` wrapper. agent-browser and desktop-contr
162162

163163
Drivers can be combined in one workflow — e.g., `tctl` for a CLI and `agent-browser` for a web UI it interacts with.
164164

165+
## Degraded-tail repro recipe (droid TUI)
166+
167+
Deterministic recipe for reproducing degraded transcript tails in the droid CLI — stranded live tool rows and queued steering messages — without waiting for a slow model turn. The trick: a slow `PreToolUse` hook pins a tool in its executing state for as long as you need.
168+
169+
1. **Scratch project.** Create a throwaway directory (never a real repo — the hook fires on every matching tool call) with a project-local hook that sleeps:
170+
171+
```bash
172+
SCRATCH="$(mktemp -d /tmp/degraded-tail-XXXXXX)"
173+
mkdir -p "$SCRATCH/.factory"
174+
cat > "$SCRATCH/.factory/settings.json" <<'JSON'
175+
{
176+
"hooks": {
177+
"PreToolUse": [
178+
{
179+
"matcher": "TodoWrite",
180+
"hooks": [{ "type": "command", "command": "sleep 120" }]
181+
}
182+
]
183+
}
184+
}
185+
JSON
186+
```
187+
188+
Pick a sleep long enough to interact mid-hook (60–180s) and a matcher for a tool the prompt will reliably trigger (`TodoWrite` fires on any multi-step ask).
189+
190+
2. **Launch with `--cwd` pointed at the scratch project** — `--repo-root` stays on your dev worktree so `droid-dev` provenance still records the code under test:
191+
192+
```bash
193+
$TCTL launch "droid-dev" -s ${RUN_ID}-tail --cwd "$SCRATCH" \
194+
--repo-root /abs/path/to/dev/worktree --record ${RUN_DIR}/tail.cast
195+
```
196+
197+
3. **Trigger the hook**, then degrade the tail while the tool row shows executing:
198+
- **Interrupt mid-hook** (`press escape`) — strands the live tool row: it never resolves to a completed/canceled state in the transcript tail.
199+
- **Steer mid-hook** (`type "..."` + `press enter`) — the steering message queues behind the executing tool instead of interleaving.
200+
201+
**Gotcha:** dev-scope hook settings can silently disable project hooks. If the tool completes instantly, run `/hooks` in the session and check the "Hooks enabled" toggle before debugging the hook config itself.
202+
165203
## Prerequisites
166204
167205
| Stage | Platform | Required | Optional |

plugins/droid-control/skills/tuistory/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ $TCTL -s demo close
5959
| `snapshot [--trim]` | Print cleaned text (`--trim` strips trailing blanks) |
6060
| `close` | Tear down session |
6161

62-
Launch options: `--cols <n>`, `--rows <n>`, `--cwd <path>`, `--env KEY=VALUE`, `--record <path>`.
62+
Launch options: `--cols <n>`, `--rows <n>`, `--cwd <path>` (child working directory; defaults to `--repo-root` when set), `--env KEY=VALUE`, `--record <path>`.
6363

6464
## Recording
6565

0 commit comments

Comments
 (0)