okstra 0.18.3 → 0.19.1
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/docs/kr/architecture.md +12 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/codex-worker.md +8 -8
- package/runtime/agents/workers/gemini-worker.md +8 -8
- package/runtime/bin/okstra-codex-exec.sh +95 -6
- package/runtime/bin/okstra-gemini-exec.sh +79 -8
- package/runtime/python/okstra_ctl/render.py +6 -1
- package/runtime/python/okstra_ctl/run.py +19 -4
- package/runtime/skills/okstra-logs/SKILL.md +167 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
package/docs/kr/architecture.md
CHANGED
|
@@ -105,10 +105,20 @@ okstra 의 prepare 책임은 단일 python 진입점 [`okstra_ctl.run.prepare_ta
|
|
|
105
105
|
|
|
106
106
|
### Python module 진입점 (single authority)
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
> **호출 규약.** 아래 `python3 -m okstra_ctl.*` / `python3 -m okstra_project.*` 형태는 **모듈 식별자**일 뿐이며, 시스템 site-packages에 설치되지 않습니다. 직접 셸에서 호출하려면 먼저 `PYTHONPATH` 를 `~/.okstra/lib/python` 으로 export 해야 합니다:
|
|
109
|
+
>
|
|
110
|
+
> ```bash
|
|
111
|
+
> eval "$(okstra paths --shell)" # OKSTRA_PYTHONPATH 등을 export
|
|
112
|
+
> export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
113
|
+
> python3 -m okstra_ctl.run --help # 이제 동작
|
|
114
|
+
> ```
|
|
115
|
+
>
|
|
116
|
+
> 위 두 줄을 생략하면 `ModuleNotFoundError: No module named 'okstra_ctl'` 로 즉시 실패합니다 (실제 implementation phase 워커가 docs 만 보고 직접 호출하다가 자주 겪는 패턴). 일반 사용자/워커는 모듈을 직접 부르지 말고 `scripts/okstra.sh` 또는 `/okstra-run` 진입점을 사용하세요 — 그 wrapper 들이 PYTHONPATH 세팅을 자동으로 해 줍니다.
|
|
117
|
+
|
|
118
|
+
- [`okstra_ctl.run`](scripts/okstra_ctl/run.py) — `prepare_task_bundle()` orchestrator + argparse CLI (`python3 -m okstra_ctl.run --workspace-root ... --project-root ... ...`, **PYTHONPATH 세팅 필요 — 위 호출 규약 참조**).
|
|
109
119
|
- [`okstra_ctl.paths`](scripts/okstra_ctl/paths.py) — `compute_run_paths()` pure path/seq 계산.
|
|
110
120
|
- [`okstra_ctl.run_context`](scripts/okstra_ctl/run_context.py) — `compute_and_write_run_context()`, `write_run_inputs()`, per-task mutex.
|
|
111
|
-
- [`okstra_ctl.render`](scripts/okstra_ctl/render.py) — task-manifest / run-manifest / timeline / task-index / team-state / launch.template / reference-expectations / discovery 9개 render 함수 + `python3 -m okstra_ctl.render <subcommand>` dispatcher.
|
|
121
|
+
- [`okstra_ctl.render`](scripts/okstra_ctl/render.py) — task-manifest / run-manifest / timeline / task-index / team-state / launch.template / reference-expectations / discovery 9개 render 함수 + `python3 -m okstra_ctl.render <subcommand>` dispatcher (**PYTHONPATH 세팅 필요 — 위 호출 규약 참조**).
|
|
112
122
|
- [`okstra_ctl.workers`](scripts/okstra_ctl/workers.py) · [`okstra_ctl.models`](scripts/okstra_ctl/models.py) — worker / model 해소.
|
|
113
123
|
- [`okstra_ctl.workflow`](scripts/okstra_ctl/workflow.py) — phase rules (PHASE_ALLOWED_OUTPUTS / PHASE_FORBIDDEN_ACTIONS).
|
|
114
124
|
- [`okstra_ctl.material`](scripts/okstra_ctl/material.py) — `analysis-material.md` 본문 + related-tasks 빌더.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -77,16 +77,16 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
|
|
|
77
77
|
```
|
|
78
78
|
Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path. The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. Omitting it during implementation will cause every Edit/Write to fail with EPERM. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, the stdin redirect from the prompt file, and stderr suppression internally. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
|
|
79
79
|
|
|
80
|
-
**Poll loop (
|
|
81
|
-
- Record `start_ts` at dispatch time.
|
|
80
|
+
**Poll loop (BashOutput-only, 30-minute hard cap):**
|
|
81
|
+
- Record `start_ts` at dispatch time via a single `Bash` call: `date +%s` (output captured).
|
|
82
82
|
- Repeat:
|
|
83
|
-
1.
|
|
84
|
-
2.
|
|
85
|
-
3. If `
|
|
86
|
-
4.
|
|
87
|
-
5. Otherwise continue polling.
|
|
83
|
+
1. Call `BashOutput(bash_id: <shell_id>)`. Inspect `status`. The harness's `BashOutput` primitive already waits internally for new output before returning; back-to-back calls are the canonical wait mechanism for a background shell.
|
|
84
|
+
2. If `status == "completed"`: break out of the loop and proceed to step 8.
|
|
85
|
+
3. If wall-clock elapsed (`current_ts - start_ts`) exceeds `1800` seconds: call `KillShell(shell_id: <shell_id>)`, then record a `cli-failure` event with `--error-type cli-failure`, `--exit-code 124`, `--duration-ms 1800000`, `--message "okstra-codex-exec.sh exceeded 30m polling cap"`, and return `CODEX_CLI_TIMEOUT: codex exec exceeded 30-minute polling cap`.
|
|
86
|
+
4. Otherwise continue polling. Read `current_ts` cheaply via another `Bash` call (`date +%s`) at most once per poll iteration.
|
|
88
87
|
- Do NOT abort the loop on transient `running` status. Only `completed` or the 30-minute cap end it.
|
|
89
88
|
- Do NOT issue parallel `BashOutput` calls or speculate about progress between polls.
|
|
89
|
+
- **No standalone `sleep` between polls.** The harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps to work around it. `BashOutput` itself is the wait primitive — calling it again immediately after a `running` status is correct. If you find yourself wanting to "slow down" the loop, that desire is a leftover from the retired 60-second-cadence rule and should be ignored.
|
|
90
90
|
|
|
91
91
|
8. Concatenate the wrapper's accumulated stdout from `BashOutput` and return it as-is without modification. If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning.
|
|
92
92
|
|
|
@@ -95,7 +95,7 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
|
|
|
95
95
|
This wrapper is a thin Bash-execution shell over the Codex CLI (via `okstra-codex-exec.sh`). The CLI process itself is the analysis engine; this subagent's only job is to dispatch it and forward output. Therefore:
|
|
96
96
|
|
|
97
97
|
- Return immediately after the polling loop exits with `completed` (or after recording any required `cli-failure` event for a non-zero exit / 30-minute cap / rate-limit).
|
|
98
|
-
- The only tool calls permitted during the polling loop are `Bash`
|
|
98
|
+
- The only tool calls permitted during the polling loop are `BashOutput`, a single `Bash` call per iteration for `date +%s` (timeout bookkeeping only — no `sleep`), and — on the timeout path only — `KillShell`. Do NOT perform additional `Read`, `Grep`, `Glob` calls between polls; do NOT inspect intermediate wrapper output mid-run.
|
|
99
99
|
- Outside the polling loop, no `Read`, `Grep`, or `Glob` beyond what is strictly required by steps 1–7 (prompt persistence, Project Root extraction, model resolution).
|
|
100
100
|
- Do NOT re-invoke `okstra-codex-exec.sh` to "double-check" or "rerun for safety" — convergence (Phase 5.5) handles cross-worker reconciliation. A single CLI dispatch per dispatched-prompt is the contract.
|
|
101
101
|
|
|
@@ -77,16 +77,16 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
|
|
|
77
77
|
```
|
|
78
78
|
Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path. The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-p -`, `-m`, `-o text`, `--include-directories`, the stdin redirect from the prompt file, and stderr suppression internally. Calling `gemini` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(gemini:*)` and produce a permission prompt every dispatch.
|
|
79
79
|
|
|
80
|
-
**Poll loop (
|
|
81
|
-
- Record `start_ts` at dispatch time.
|
|
80
|
+
**Poll loop (BashOutput-only, 30-minute hard cap):**
|
|
81
|
+
- Record `start_ts` at dispatch time via a single `Bash` call: `date +%s` (output captured).
|
|
82
82
|
- Repeat:
|
|
83
|
-
1.
|
|
84
|
-
2.
|
|
85
|
-
3. If `
|
|
86
|
-
4.
|
|
87
|
-
5. Otherwise continue polling.
|
|
83
|
+
1. Call `BashOutput(bash_id: <shell_id>)`. Inspect `status`. The harness's `BashOutput` primitive already waits internally for new output before returning; back-to-back calls are the canonical wait mechanism for a background shell.
|
|
84
|
+
2. If `status == "completed"`: break out of the loop and proceed to step 8.
|
|
85
|
+
3. If wall-clock elapsed (`current_ts - start_ts`) exceeds `1800` seconds: call `KillShell(shell_id: <shell_id>)`, then record a `cli-failure` event with `--error-type cli-failure`, `--exit-code 124`, `--duration-ms 1800000`, `--message "okstra-gemini-exec.sh exceeded 30m polling cap"`, and return `GEMINI_CLI_TIMEOUT: gemini exec exceeded 30-minute polling cap`.
|
|
86
|
+
4. Otherwise continue polling. Read `current_ts` cheaply via another `Bash` call (`date +%s`) at most once per poll iteration.
|
|
88
87
|
- Do NOT abort the loop on transient `running` status. Only `completed` or the 30-minute cap end it.
|
|
89
88
|
- Do NOT issue parallel `BashOutput` calls or speculate about progress between polls.
|
|
89
|
+
- **No standalone `sleep` between polls.** The harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps to work around it. `BashOutput` itself is the wait primitive — calling it again immediately after a `running` status is correct. If you find yourself wanting to "slow down" the loop, that desire is a leftover from the retired 60-second-cadence rule and should be ignored.
|
|
90
90
|
|
|
91
91
|
8. Concatenate the wrapper's accumulated stdout from `BashOutput` and return it as-is without modification. If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning.
|
|
92
92
|
|
|
@@ -95,7 +95,7 @@ The wrapper exists because Claude Code's Bash permission matcher rejects simple-
|
|
|
95
95
|
This wrapper is a thin Bash-execution shell over the Gemini CLI (via `okstra-gemini-exec.sh`). The CLI process itself is the analysis engine; this subagent's only job is to dispatch it and forward output. Therefore:
|
|
96
96
|
|
|
97
97
|
- Return immediately after the polling loop exits with `completed` (or after recording any required `cli-failure` event for a non-zero exit / 30-minute cap / rate-limit).
|
|
98
|
-
- The only tool calls permitted during the polling loop are `Bash`
|
|
98
|
+
- The only tool calls permitted during the polling loop are `BashOutput`, a single `Bash` call per iteration for `date +%s` (timeout bookkeeping only — no `sleep`), and — on the timeout path only — `KillShell`. Do NOT perform additional `Read`, `Grep`, `Glob` calls between polls; do NOT inspect intermediate wrapper output mid-run.
|
|
99
99
|
- Outside the polling loop, no `Read`, `Grep`, or `Glob` beyond what is strictly required by steps 1–7 (prompt persistence, Project Root extraction, model resolution).
|
|
100
100
|
- Do NOT re-invoke `okstra-gemini-exec.sh` to "double-check" or "rerun for safety" — convergence (Phase 5.5) handles cross-worker reconciliation. A single CLI dispatch per dispatched-prompt is the contract.
|
|
101
101
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# Bash($HOME/.okstra/bin/okstra-codex-exec.sh:*)
|
|
14
14
|
#
|
|
15
15
|
# Usage:
|
|
16
|
-
# okstra-codex-exec.sh <project-root> <model-execution-value> <prompt-path> [worktree-path]
|
|
16
|
+
# okstra-codex-exec.sh <project-root> <model-execution-value> <prompt-path> [worktree-path] [role]
|
|
17
17
|
#
|
|
18
18
|
# project-root / model-execution-value / prompt-path are required.
|
|
19
19
|
#
|
|
@@ -24,11 +24,28 @@
|
|
|
24
24
|
# directory alongside the primary workspace anchored at project-root. When
|
|
25
25
|
# omitted or empty, no `--add-dir` is added (existing analysis-phase behavior).
|
|
26
26
|
#
|
|
27
|
+
# role is optional and used only to label the auto-spawned tmux trace pane
|
|
28
|
+
# (see "trace pane" section below). When omitted, it defaults to `executor`
|
|
29
|
+
# if worktree-path is non-empty (the implementation-phase invariant) and
|
|
30
|
+
# `worker` otherwise.
|
|
31
|
+
#
|
|
32
|
+
# For linked worktrees (the okstra implementation default), the per-worktree
|
|
33
|
+
# git metadata (index, HEAD, refs) and the shared object database live in the
|
|
34
|
+
# main repository's `.git` directory — OUTSIDE the worktree-path. Without
|
|
35
|
+
# write access there, `git add` / `git commit` from inside the worktree fails
|
|
36
|
+
# with EPERM on `.git/worktrees/<name>/index.lock`, which is the documented
|
|
37
|
+
# failure pattern for linked-worktree commits under `workspace-write`. The
|
|
38
|
+
# wrapper resolves the main repo's git-common-dir via `git rev-parse` against
|
|
39
|
+
# the supplied worktree-path and forwards it as an additional `--add-dir`. If
|
|
40
|
+
# resolution fails (not a linked worktree, or git unavailable), the extra
|
|
41
|
+
# add-dir is silently omitted — the caller still gets the worktree add-dir
|
|
42
|
+
# and any commit failure surfaces as a normal sandbox EPERM.
|
|
43
|
+
#
|
|
27
44
|
# The wrapper exits non-zero on any preflight failure.
|
|
28
45
|
set -euo pipefail
|
|
29
46
|
|
|
30
|
-
if [[ $# -lt 3 || $# -gt
|
|
31
|
-
printf 'usage: %s <project-root> <model-execution-value> <prompt-path> [worktree-path]\n' "$(basename "$0")" >&2
|
|
47
|
+
if [[ $# -lt 3 || $# -gt 5 ]]; then
|
|
48
|
+
printf 'usage: %s <project-root> <model-execution-value> <prompt-path> [worktree-path] [role]\n' "$(basename "$0")" >&2
|
|
32
49
|
exit 64
|
|
33
50
|
fi
|
|
34
51
|
|
|
@@ -36,6 +53,10 @@ project_root="$1"
|
|
|
36
53
|
model="$2"
|
|
37
54
|
prompt_path="$3"
|
|
38
55
|
worktree_path="${4-}"
|
|
56
|
+
role="${5-}"
|
|
57
|
+
if [[ -z "$role" ]]; then
|
|
58
|
+
if [[ -n "$worktree_path" ]]; then role="executor"; else role="worker"; fi
|
|
59
|
+
fi
|
|
39
60
|
|
|
40
61
|
if [[ -z "$project_root" || ! -d "$project_root" ]]; then
|
|
41
62
|
printf 'okstra-codex-exec: project-root is missing or not a directory: %q\n' "$project_root" >&2
|
|
@@ -65,8 +86,76 @@ fi
|
|
|
65
86
|
extra_args=()
|
|
66
87
|
if [[ -n "$worktree_path" ]]; then
|
|
67
88
|
extra_args+=(--add-dir "$worktree_path")
|
|
89
|
+
# For linked worktrees, also open the main repo's `.git` so `git add` /
|
|
90
|
+
# `git commit` can write the per-worktree index/refs (under
|
|
91
|
+
# `.git/worktrees/<name>/`) and the shared object DB (`.git/objects/`).
|
|
92
|
+
# `--git-common-dir` resolves to the main repo's `.git` for any worktree
|
|
93
|
+
# (linked or main); for a main checkout it equals `<worktree>/.git` and is
|
|
94
|
+
# redundant-but-harmless. Failures (not-a-git-repo, git missing) are
|
|
95
|
+
# tolerated silently so analysis-phase callers stay unaffected.
|
|
96
|
+
if command -v git >/dev/null 2>&1; then
|
|
97
|
+
common_git_dir=$(git -C "$worktree_path" rev-parse --git-common-dir 2>/dev/null || true)
|
|
98
|
+
if [[ -n "$common_git_dir" ]]; then
|
|
99
|
+
# `rev-parse --git-common-dir` may return a path relative to the
|
|
100
|
+
# worktree; normalise to an absolute directory before forwarding.
|
|
101
|
+
if [[ "$common_git_dir" != /* ]]; then
|
|
102
|
+
common_git_dir="$worktree_path/$common_git_dir"
|
|
103
|
+
fi
|
|
104
|
+
if [[ -d "$common_git_dir" ]]; then
|
|
105
|
+
# Resolve `..` / symlinks so codex sees a canonical path.
|
|
106
|
+
common_git_dir=$(cd "$common_git_dir" && pwd -P)
|
|
107
|
+
extra_args+=(--add-dir "$common_git_dir")
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
68
111
|
fi
|
|
69
112
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
|
|
113
|
+
# Derive a live-progress log path next to the prompt. The codex CLI streams
|
|
114
|
+
# its progress over stdout/stderr, but the caller (codex-worker subagent)
|
|
115
|
+
# only polls `BashOutput` on a 60s cadence — so without a sideband, a 10–30
|
|
116
|
+
# minute implementation run produces no visible output until the very end.
|
|
117
|
+
# Mirroring both streams into a file alongside the prompt lets the human
|
|
118
|
+
# operator `tail -f <log-path>` from a separate pane and watch progress in
|
|
119
|
+
# real time, and leaves a post-mortem record on disk regardless of how the
|
|
120
|
+
# subagent renders the dispatch.
|
|
121
|
+
log_path="${prompt_path%.md}.log"
|
|
122
|
+
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
123
|
+
: > "$log_path"
|
|
124
|
+
|
|
125
|
+
# When a tmux session is reachable, split a sibling pane that tails the live
|
|
126
|
+
# log so the operator can watch codex's progress in real time without waiting
|
|
127
|
+
# for the wrapper to exit. This fires in every phase the wrapper is invoked
|
|
128
|
+
# from (analysis, error-analysis, implementation-planning, implementation,
|
|
129
|
+
# …) — long-running codex dispatches are not implementation-specific. The
|
|
130
|
+
# new pane carries the title `codex-<role>-trace` (e.g. `codex-worker-trace`
|
|
131
|
+
# in analysis, `codex-executor-trace` in implementation) and uses `tail -F`
|
|
132
|
+
# (follow-by-name) so it survives any truncation a re-dispatch performs on
|
|
133
|
+
# the same log path. Failures are tolerated silently: missing $TMUX, a tmux
|
|
134
|
+
# that refuses to split (size constraints, locked client), or a stale socket
|
|
135
|
+
# all degrade to "log file is still on disk; the operator can tail it
|
|
136
|
+
# manually from any terminal." The wrapper does NOT switch focus to the new
|
|
137
|
+
# pane — control returns to the caller's pane via `tmux last-pane`.
|
|
138
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
139
|
+
trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
|
|
140
|
+
-c "$(dirname "$log_path")" \
|
|
141
|
+
"tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
|
|
142
|
+
if [[ -n "$trace_pane" ]]; then
|
|
143
|
+
tmux select-pane -t "$trace_pane" -T "codex-${role}-trace" 2>/dev/null || true
|
|
144
|
+
tmux last-pane 2>/dev/null || true
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# stdin redirect, stderr capture, and pipeline mirroring are intentionally
|
|
149
|
+
# inside the wrapper — this is the entire reason this script exists.
|
|
150
|
+
#
|
|
151
|
+
# stdout: tee'd to both the log file (for `tail -f`) AND the wrapper's own
|
|
152
|
+
# stdout (so the subagent's `BashOutput` still captures the final
|
|
153
|
+
# text verbatim for Phase 5 synthesis).
|
|
154
|
+
# stderr: appended to the log file only — mirrors the prior `2>/dev/null`
|
|
155
|
+
# contract of keeping the wrapper's stderr stream clean.
|
|
156
|
+
# exit: `PIPESTATUS[0]` preserves codex's own exit code (tee always 0).
|
|
157
|
+
{
|
|
158
|
+
codex exec -C "$project_root" ${extra_args[@]+"${extra_args[@]}"} --model "$model" --sandbox workspace-write - \
|
|
159
|
+
< "$prompt_path" 2>> "$log_path"
|
|
160
|
+
} | tee -a "$log_path"
|
|
161
|
+
exit "${PIPESTATUS[0]}"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# Bash($HOME/.okstra/bin/okstra-gemini-exec.sh:*)
|
|
14
14
|
#
|
|
15
15
|
# Usage:
|
|
16
|
-
# okstra-gemini-exec.sh <project-root> <model-execution-value> <prompt-path> [worktree-path]
|
|
16
|
+
# okstra-gemini-exec.sh <project-root> <model-execution-value> <prompt-path> [worktree-path] [role]
|
|
17
17
|
#
|
|
18
18
|
# project-root / model-execution-value / prompt-path are required.
|
|
19
19
|
#
|
|
@@ -24,11 +24,20 @@
|
|
|
24
24
|
# operate on the worktree alongside the primary workspace. When omitted or
|
|
25
25
|
# empty, only project-root is included (existing analysis-phase behavior).
|
|
26
26
|
#
|
|
27
|
+
# For linked worktrees, the per-worktree git metadata (index, HEAD, refs) and
|
|
28
|
+
# the shared object database live in the main repository's `.git` directory —
|
|
29
|
+
# OUTSIDE the worktree-path. Without access there, `git add` / `git commit`
|
|
30
|
+
# from inside the worktree fails on `.git/worktrees/<name>/index.lock`. The
|
|
31
|
+
# wrapper resolves the main repo's git-common-dir via `git rev-parse` against
|
|
32
|
+
# the supplied worktree-path and appends it to `--include-directories`. If
|
|
33
|
+
# resolution fails (not a linked worktree, or git unavailable), the extra
|
|
34
|
+
# include is silently omitted.
|
|
35
|
+
#
|
|
27
36
|
# The wrapper exits non-zero on any preflight failure.
|
|
28
37
|
set -euo pipefail
|
|
29
38
|
|
|
30
|
-
if [[ $# -lt 3 || $# -gt
|
|
31
|
-
printf 'usage: %s <project-root> <model-execution-value> <prompt-path> [worktree-path]\n' "$(basename "$0")" >&2
|
|
39
|
+
if [[ $# -lt 3 || $# -gt 5 ]]; then
|
|
40
|
+
printf 'usage: %s <project-root> <model-execution-value> <prompt-path> [worktree-path] [role]\n' "$(basename "$0")" >&2
|
|
32
41
|
exit 64
|
|
33
42
|
fi
|
|
34
43
|
|
|
@@ -36,6 +45,10 @@ project_root="$1"
|
|
|
36
45
|
model="$2"
|
|
37
46
|
prompt_path="$3"
|
|
38
47
|
worktree_path="${4-}"
|
|
48
|
+
role="${5-}"
|
|
49
|
+
if [[ -z "$role" ]]; then
|
|
50
|
+
if [[ -n "$worktree_path" ]]; then role="executor"; else role="worker"; fi
|
|
51
|
+
fi
|
|
39
52
|
|
|
40
53
|
if [[ -z "$project_root" || ! -d "$project_root" ]]; then
|
|
41
54
|
printf 'okstra-gemini-exec: project-root is missing or not a directory: %q\n' "$project_root" >&2
|
|
@@ -65,10 +78,68 @@ fi
|
|
|
65
78
|
include_dirs="$project_root"
|
|
66
79
|
if [[ -n "$worktree_path" ]]; then
|
|
67
80
|
include_dirs="$project_root,$worktree_path"
|
|
81
|
+
# For linked worktrees, also open the main repo's `.git` so `git add` /
|
|
82
|
+
# `git commit` can write the per-worktree index/refs and the shared
|
|
83
|
+
# object DB. `--git-common-dir` resolves to the main repo's `.git` for
|
|
84
|
+
# any worktree (linked or main); for a main checkout it equals
|
|
85
|
+
# `<worktree>/.git` and is redundant-but-harmless. Failures are tolerated
|
|
86
|
+
# silently so analysis-phase callers stay unaffected.
|
|
87
|
+
if command -v git >/dev/null 2>&1; then
|
|
88
|
+
common_git_dir=$(git -C "$worktree_path" rev-parse --git-common-dir 2>/dev/null || true)
|
|
89
|
+
if [[ -n "$common_git_dir" ]]; then
|
|
90
|
+
if [[ "$common_git_dir" != /* ]]; then
|
|
91
|
+
common_git_dir="$worktree_path/$common_git_dir"
|
|
92
|
+
fi
|
|
93
|
+
if [[ -d "$common_git_dir" ]]; then
|
|
94
|
+
common_git_dir=$(cd "$common_git_dir" && pwd -P)
|
|
95
|
+
include_dirs="$include_dirs,$common_git_dir"
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Derive a live-progress log path next to the prompt. The gemini CLI streams
|
|
102
|
+
# its progress over stdout/stderr, but the caller (gemini-worker subagent)
|
|
103
|
+
# only polls `BashOutput` on a 60s cadence — so without a sideband, a long
|
|
104
|
+
# implementation run produces no visible output until the very end. Mirroring
|
|
105
|
+
# both streams into a file alongside the prompt lets the human operator
|
|
106
|
+
# `tail -f <log-path>` from a separate pane and watch progress in real time,
|
|
107
|
+
# and leaves a post-mortem record on disk.
|
|
108
|
+
log_path="${prompt_path%.md}.log"
|
|
109
|
+
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
110
|
+
: > "$log_path"
|
|
111
|
+
|
|
112
|
+
# When a tmux session is reachable, split a sibling pane tailing the log so
|
|
113
|
+
# the operator can watch progress live. This fires in every phase the
|
|
114
|
+
# wrapper is invoked from — long-running gemini dispatches are not
|
|
115
|
+
# implementation-specific. Title `gemini-<role>-trace` (e.g.
|
|
116
|
+
# `gemini-worker-trace` in analysis, `gemini-executor-trace` in
|
|
117
|
+
# implementation). See the codex wrapper for the full design rationale and
|
|
118
|
+
# the silent-degrade failure model.
|
|
119
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
120
|
+
trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
|
|
121
|
+
-c "$(dirname "$log_path")" \
|
|
122
|
+
"tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
|
|
123
|
+
if [[ -n "$trace_pane" ]]; then
|
|
124
|
+
tmux select-pane -t "$trace_pane" -T "gemini-${role}-trace" 2>/dev/null || true
|
|
125
|
+
tmux last-pane 2>/dev/null || true
|
|
126
|
+
fi
|
|
68
127
|
fi
|
|
69
128
|
|
|
70
|
-
# stdin redirect and
|
|
71
|
-
# this is the entire reason this script exists. Gemini
|
|
72
|
-
# so workspace correctness is anchored via
|
|
73
|
-
# Project Root referenced in the prompt
|
|
74
|
-
|
|
129
|
+
# stdin redirect, stderr capture, and pipeline mirroring are intentionally
|
|
130
|
+
# inside the wrapper — this is the entire reason this script exists. Gemini
|
|
131
|
+
# CLI has no `--cd` flag, so workspace correctness is anchored via
|
|
132
|
+
# `--include-directories` plus the Project Root referenced in the prompt
|
|
133
|
+
# body itself.
|
|
134
|
+
#
|
|
135
|
+
# stdout: tee'd to both the log file (for `tail -f`) AND the wrapper's own
|
|
136
|
+
# stdout (so the subagent's `BashOutput` still captures the final
|
|
137
|
+
# text verbatim for Phase 5 synthesis).
|
|
138
|
+
# stderr: appended to the log file only — mirrors the prior `2>/dev/null`
|
|
139
|
+
# contract of keeping the wrapper's stderr stream clean.
|
|
140
|
+
# exit: `PIPESTATUS[0]` preserves gemini's own exit code (tee always 0).
|
|
141
|
+
{
|
|
142
|
+
gemini -p - -m "$model" -o text --include-directories "$include_dirs" \
|
|
143
|
+
< "$prompt_path" 2>> "$log_path"
|
|
144
|
+
} | tee -a "$log_path"
|
|
145
|
+
exit "${PIPESTATUS[0]}"
|
|
@@ -1156,7 +1156,12 @@ def render_template_file(template_path: str, output_path: str, ctx: dict) -> Non
|
|
|
1156
1156
|
|
|
1157
1157
|
def main(argv: list[str]) -> int:
|
|
1158
1158
|
if not argv:
|
|
1159
|
-
print(
|
|
1159
|
+
print(
|
|
1160
|
+
"usage: python3 -m okstra_ctl.render <subcommand> ...\n"
|
|
1161
|
+
" (requires PYTHONPATH=$(okstra paths --field python); "
|
|
1162
|
+
"normal callers go through scripts/okstra.sh instead)",
|
|
1163
|
+
file=sys.stderr,
|
|
1164
|
+
)
|
|
1160
1165
|
return 2
|
|
1161
1166
|
sub = argv[0]
|
|
1162
1167
|
rest = argv[1:]
|
|
@@ -61,9 +61,18 @@ from .workers import normalize_workers, resolve_profile_workers
|
|
|
61
61
|
from .workflow import compute_workflow_state
|
|
62
62
|
from .worktree import provision_task_worktree
|
|
63
63
|
|
|
64
|
+
# Validator regex for the approval marker.
|
|
65
|
+
#
|
|
66
|
+
# Tolerates a single optional backtick on either side of the approval token,
|
|
67
|
+
# because the report template instructs the user to flip `[ ]` to `[x]` inside
|
|
68
|
+
# a markdown code span and the report-writer worker often emits a standalone
|
|
69
|
+
# marker line wrapped the same way (e.g. `- ` + backtick + `[x] Approved` +
|
|
70
|
+
# backtick). Backticks carry no semantic content here — stripping them at the
|
|
71
|
+
# parser level is simpler than threading a "please remove formatting" rule
|
|
72
|
+
# through every authoring surface.
|
|
64
73
|
APPROVED_PLAN_PATTERN = re.compile(
|
|
65
|
-
r"^[ \t]*(?:[-*+][ \t]+)
|
|
66
|
-
r"User[ \t]+Approval[ \t]*:[ \t]*(APPROVED|granted|yes))",
|
|
74
|
+
r"^[ \t]*(?:[-*+][ \t]+)?`?(APPROVED([ \t]|:|$|`)|\[x\][ \t]*Approved`?|"
|
|
75
|
+
r"User[ \t]+Approval[ \t]*:[ \t]*(APPROVED|granted|yes)`?)",
|
|
67
76
|
re.IGNORECASE | re.MULTILINE,
|
|
68
77
|
)
|
|
69
78
|
|
|
@@ -127,8 +136,14 @@ def _validate_approved_plan(path: str) -> None:
|
|
|
127
136
|
|
|
128
137
|
# `- [ ] Approved` 라인을 정확히 한 번만 매치한다. 좌측 leading whitespace 와
|
|
129
138
|
# 옵션 bullet 은 보존된 채 체크박스 안쪽 공백만 `x` 로 갱신된다.
|
|
139
|
+
#
|
|
140
|
+
# Group 1: leading whitespace + optional bullet + optional opening backtick.
|
|
141
|
+
# Group 2: optional closing backtick + trailing whitespace.
|
|
142
|
+
# Both groups are preserved verbatim in the replacement so a backtick-wrapped
|
|
143
|
+
# `- \`[ ] Approved\`` flips to `- \`[x] Approved\`` without losing the
|
|
144
|
+
# surrounding code span — the validator regex tolerates either form.
|
|
130
145
|
APPROVAL_UNCHECKED_PATTERN = re.compile(
|
|
131
|
-
r"^([ \t]*(?:[-*+][ \t]+)
|
|
146
|
+
r"^([ \t]*(?:[-*+][ \t]+)?`?)\[[ \t]\][ \t]*Approved(`?[ \t]*)$",
|
|
132
147
|
re.IGNORECASE | re.MULTILINE,
|
|
133
148
|
)
|
|
134
149
|
|
|
@@ -162,7 +177,7 @@ def _apply_cli_approval(path: str) -> str:
|
|
|
162
177
|
|
|
163
178
|
if APPROVAL_UNCHECKED_PATTERN.search(body):
|
|
164
179
|
new_body, count = APPROVAL_UNCHECKED_PATTERN.subn(
|
|
165
|
-
lambda m: f"{m.group(1)}[x] Approved", body, count=1,
|
|
180
|
+
lambda m: f"{m.group(1)}[x] Approved{m.group(2)}", body, count=1,
|
|
166
181
|
)
|
|
167
182
|
new_body = new_body.rstrip("\n") + "\n" + audit_line + "\n"
|
|
168
183
|
p.write_text(new_body, encoding="utf-8")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: okstra-logs
|
|
3
|
+
description: Use when the user asks about okstra worker wrapper log files — listing, sizes, ages, disk usage, or wants to know what `*.log` sidecars exist for past dispatches and which ones are safe to clean up. Trigger words include "okstra logs", "로그 현황", "로그 파일", "log files", "log size", "log status", "로그 정리", "log cleanup".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OKSTRA Logs
|
|
7
|
+
|
|
8
|
+
Read-only inventory of codex/gemini wrapper log files written next to each
|
|
9
|
+
prompt history file (`<prompt>.log`). Reports sizes, ages, totals, and
|
|
10
|
+
suggests cleanup commands. **Does not delete** — the user runs whichever
|
|
11
|
+
`find … -delete` line they like.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- The user wants to see how much disk space okstra wrapper logs consume.
|
|
16
|
+
- The user wants to know which tasks / phases / workers have lingering log
|
|
17
|
+
sidecars from past dispatches.
|
|
18
|
+
- The user is planning a cleanup and wants ready-to-run `find` commands
|
|
19
|
+
scoped by age, task-id, or task-group.
|
|
20
|
+
|
|
21
|
+
## Background
|
|
22
|
+
|
|
23
|
+
Codex/gemini wrappers (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`)
|
|
24
|
+
write a sidecar log next to each prompt history file:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
.project-docs/okstra/tasks/<task-group>/<task-id>/runs/<phase>/prompts/
|
|
28
|
+
<worker>-prompt-<phase>-<seq>.md <-- prompt (git-tracked)
|
|
29
|
+
<worker>-prompt-<phase>-<seq>.log <-- live stdout+stderr mirror
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The log is truncated at each dispatch (`: > "$log_path"`) — only the latest
|
|
33
|
+
run for a given seq is preserved. Different seqs (`-001`, `-002`, …) keep
|
|
34
|
+
separate files. Long-running implementation dispatches can produce
|
|
35
|
+
multi-MB logs; analysis-phase dispatches are typically smaller.
|
|
36
|
+
|
|
37
|
+
## Step 0: Verify okstra runtime + project setup
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
if command -v okstra >/dev/null 2>&1; then
|
|
41
|
+
OKSTRA_CMD="okstra"
|
|
42
|
+
else
|
|
43
|
+
OKSTRA_CMD="npx -y okstra@latest"
|
|
44
|
+
fi
|
|
45
|
+
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
46
|
+
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
50
|
+
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
51
|
+
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Parse `projectRoot` from the JSON and use it as the search root for the
|
|
57
|
+
steps below.
|
|
58
|
+
|
|
59
|
+
## Step 1: Inventory
|
|
60
|
+
|
|
61
|
+
Find all wrapper log files and collect metadata. Use a single `find` to
|
|
62
|
+
keep the I/O cost predictable, then format the results.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
PROJECT_ROOT=$(echo "$OKSTRA_PROJECT_INFO" | python3 -c 'import sys,json;print(json.load(sys.stdin)["projectRoot"])')
|
|
66
|
+
LOGS_ROOT="$PROJECT_ROOT/.project-docs/okstra/tasks"
|
|
67
|
+
|
|
68
|
+
# columns: size_bytes | mtime_epoch | path
|
|
69
|
+
find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' \
|
|
70
|
+
-printf '%s\t%T@\t%p\n' 2>/dev/null \
|
|
71
|
+
| sort -k2,2nr
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
On macOS, `find -printf` is unavailable. Fall back to `stat`:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' 2>/dev/null \
|
|
78
|
+
| while IFS= read -r p; do
|
|
79
|
+
stat -f '%z%t%m%t%N' "$p"
|
|
80
|
+
done \
|
|
81
|
+
| sort -k2,2nr
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If the result is empty, report `No wrapper log files found under <PROJECT_ROOT>` and exit.
|
|
85
|
+
|
|
86
|
+
## Step 2: Summary table
|
|
87
|
+
|
|
88
|
+
Group results and emit two tables.
|
|
89
|
+
|
|
90
|
+
### Table A — Top 20 largest logs
|
|
91
|
+
|
|
92
|
+
| # | Task | Phase | Worker | Seq | Size | Age | Path |
|
|
93
|
+
|---|------|-------|--------|-----|------|-----|------|
|
|
94
|
+
|
|
95
|
+
Parse fields from the path:
|
|
96
|
+
- task-group / task-id: from the `tasks/<task-group>/<task-id>/` segment
|
|
97
|
+
- phase: from `runs/<phase>/`
|
|
98
|
+
- worker: from filename prefix before `-prompt-`
|
|
99
|
+
- seq: from filename suffix (last 3-digit segment)
|
|
100
|
+
|
|
101
|
+
Format sizes as human-readable (KB / MB). Format age as `Nd` (days) or
|
|
102
|
+
`Nh` (hours) from `mtime` relative to "now".
|
|
103
|
+
|
|
104
|
+
### Table B — Per-task totals
|
|
105
|
+
|
|
106
|
+
| Task Key | Files | Total Size | Oldest | Newest |
|
|
107
|
+
|----------|-------|-----------:|--------|--------|
|
|
108
|
+
|
|
109
|
+
Sort by total size descending. "Task Key" = `<project-id>:<task-group>:<task-id>` for consistency with other okstra skills.
|
|
110
|
+
|
|
111
|
+
### Footer line
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Total: N files, X.X MB across M tasks under <PROJECT_ROOT>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Step 3: Suggested cleanup commands
|
|
118
|
+
|
|
119
|
+
Emit a fenced bash block the user can copy-paste. Do NOT execute these.
|
|
120
|
+
|
|
121
|
+
```markdown
|
|
122
|
+
## Cleanup options (manual)
|
|
123
|
+
|
|
124
|
+
# 7일 이상 된 로그만 삭제
|
|
125
|
+
find <PROJECT_ROOT>/.project-docs/okstra/tasks \
|
|
126
|
+
-type f -path '*/runs/*/prompts/*.log' -mtime +7 -delete
|
|
127
|
+
|
|
128
|
+
# 30일 이상 된 로그만 삭제
|
|
129
|
+
find <PROJECT_ROOT>/.project-docs/okstra/tasks \
|
|
130
|
+
-type f -path '*/runs/*/prompts/*.log' -mtime +30 -delete
|
|
131
|
+
|
|
132
|
+
# 특정 task-group 의 로그 일괄 삭제 (예: dev-9388)
|
|
133
|
+
find <PROJECT_ROOT>/.project-docs/okstra/tasks/dev-9388 \
|
|
134
|
+
-type f -name '*.log' -delete
|
|
135
|
+
|
|
136
|
+
# 특정 task-id 의 로그 일괄 삭제 (예: dev-9428)
|
|
137
|
+
find <PROJECT_ROOT>/.project-docs/okstra/tasks/*/dev-9428 \
|
|
138
|
+
-type f -name '*.log' -delete
|
|
139
|
+
|
|
140
|
+
# 전체 일괄 삭제 (주의)
|
|
141
|
+
find <PROJECT_ROOT>/.project-docs/okstra/tasks \
|
|
142
|
+
-type f -path '*/runs/*/prompts/*.log' -delete
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Substitute the literal `<PROJECT_ROOT>` with the resolved absolute path so
|
|
146
|
+
the commands are directly copy-pasteable.
|
|
147
|
+
|
|
148
|
+
## Step 4: Notes for the user
|
|
149
|
+
|
|
150
|
+
End the response with these short reminders:
|
|
151
|
+
|
|
152
|
+
- Logs are truncated on each re-dispatch of the same `seq`, so deleting an
|
|
153
|
+
in-flight run's log will cause the wrapper to recreate an empty file on
|
|
154
|
+
the next dispatch — no data loss beyond the current trace.
|
|
155
|
+
- Prompt history files (`.md`) are separate and are NOT touched by these
|
|
156
|
+
commands — only `.log` sidecars.
|
|
157
|
+
- This skill does not modify `.gitignore`. If the project commits
|
|
158
|
+
`.project-docs/okstra/`, the user may want to add
|
|
159
|
+
`.project-docs/okstra/tasks/**/runs/**/prompts/*.log` to `.gitignore`
|
|
160
|
+
manually to keep large logs out of git.
|
|
161
|
+
|
|
162
|
+
## What this skill is NOT
|
|
163
|
+
|
|
164
|
+
- Does NOT delete log files. Only inventories and suggests commands.
|
|
165
|
+
- Does NOT touch prompt history files (`.md`), worker results, manifests,
|
|
166
|
+
or any other okstra state.
|
|
167
|
+
- Does NOT run on a schedule. Invoke explicitly when needed.
|
|
@@ -295,7 +295,7 @@ empty run-level error logs in production.
|
|
|
295
295
|
|
|
296
296
|
- `cli-failure` events are recorded by the wrapper subagent itself (Codex / Gemini), but **directly to the run-level error log** via `okstra-error-log.py append-observed --error-type cli-failure ...` — NOT via the sidecar. The sidecar is an in-process tool-failure channel only.
|
|
297
297
|
- **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-gemini-exec.sh` accept four positional arguments: `<project-root> <model> <prompt-path> [<worktree-path>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for gemini it is appended to `--include-directories`. Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt.
|
|
298
|
-
- **Background dispatch + polling contract (Codex / Gemini wrappers).** Both wrapper subagents MUST dispatch `okstra-codex-exec.sh` / `okstra-gemini-exec.sh` via `Bash(run_in_background: true)` and poll with `BashOutput(bash_id)`
|
|
298
|
+
- **Background dispatch + polling contract (Codex / Gemini wrappers).** Both wrapper subagents MUST dispatch `okstra-codex-exec.sh` / `okstra-gemini-exec.sh` via `Bash(run_in_background: true)` and poll with `BashOutput(bash_id)` until the shell reports `status == "completed"`, capped at 30 minutes (1800s) of wall-clock elapsed time. `BashOutput` itself is the wait primitive — call it back-to-back; do NOT insert a standalone `sleep` between polls. The Claude Code harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps inside until-loops to work around the block. Workers that hit the contract bug must NOT self-recover with `until ...; do sleep 2; done` wrappers — that path violates the harness anti-circumvention rule, even though it superficially "works". The legacy "single foreground `Bash` with 120000ms timeout" rule, and the subsequent "60-second cadence with `sleep 60` between polls" rule, are both retired. The current rule applies in **every phase** (analysis runs typically complete in 1–2 `BashOutput` calls, so there is no regression for short jobs). Recording responsibilities:
|
|
299
299
|
- Successful completion: return the wrapper's accumulated stdout from the final `BashOutput`. No log entry.
|
|
300
300
|
- Non-zero `exit_code` reported by `BashOutput`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
|
|
301
301
|
- 30-minute polling cap exceeded: call `KillShell(shell_id)` first, then record `cli-failure` with `--exit-code 124 --duration-ms 1800000 --message "<wrapper> exceeded 30m polling cap"`, then return the language-specific `*_CLI_TIMEOUT` sentinel.
|