Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions lane-handoff
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,38 @@ say() { printf '%s\n' "$*"; }
note() { printf '%s: %s\n' "$prog" "$*" >&2; }
step() { printf '%s: %s\n' "$prog" "$*" >&2; }
die() { printf '\n%s: REFUSED: %s\n' "$prog" "$1" >&2; exit "${2:-2}"; }
check_claude_env_dual() { # <WINDOW_REF|WINDOW_ID|LANE_DIR>
local cce_suffix="$1" cce_new_set cce_legacy_set cce_new cce_legacy
case "$cce_suffix" in
WINDOW_REF)
cce_new_set="${OPENBENCHES_CLAUDE_WINDOW_REF+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_WINDOW_REF+set}"
cce_new="${OPENBENCHES_CLAUDE_WINDOW_REF-}"
cce_legacy="${WORKBENCHES_CLAUDE_WINDOW_REF-}"
;;
WINDOW_ID)
cce_new_set="${OPENBENCHES_CLAUDE_WINDOW_ID+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_WINDOW_ID+set}"
cce_new="${OPENBENCHES_CLAUDE_WINDOW_ID-}"
cce_legacy="${WORKBENCHES_CLAUDE_WINDOW_ID-}"
;;
LANE_DIR)
cce_new_set="${OPENBENCHES_CLAUDE_LANE_DIR+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_LANE_DIR+set}"
cce_new="${OPENBENCHES_CLAUDE_LANE_DIR-}"
cce_legacy="${WORKBENCHES_CLAUDE_LANE_DIR-}"
;;
*)
printf 'REFUSED: unknown Claude environment suffix: %s\n' "$cce_suffix" >&2
return 2
;;
esac
if [ "$cce_new_set" = set ] && [ "$cce_legacy_set" = set ] && [ "$cce_new" != "$cce_legacy" ]; then
printf 'REFUSED: OPENBENCHES_CLAUDE_%s and WORKBENCHES_CLAUDE_%s have conflicting values. Set both to the same value, or unset the incorrect one, then re-run. Nothing was renamed or written.\n' \
"$cce_suffix" "$cce_suffix" >&2
return 2
fi
}
# Exact launch facts must not carry record delimiters, on Bash 3.2 too.
restart_fact_ok() {
local LC_ALL=C
Expand Down Expand Up @@ -1580,6 +1612,11 @@ lifecycle_finish() { # <what is unfinished, empty when nothing is>

# ------------------------------------------------- 1. the identity triple
#
# Ambiguous old/new launcher values refuse before identity selection or any write.
check_claude_env_dual WINDOW_REF || exit
check_claude_env_dual WINDOW_ID || exit
check_claude_env_dual LANE_DIR || exit
#
# THE WORKSTATION COMES FROM CONFIGURATION, NEVER FROM `hostname` (Amendment 11,
# ratified decision 8(d)): the helper's own `workstation` read is the one
# implementation, and its second tab-separated field says which rung answered.
Expand Down Expand Up @@ -1639,9 +1676,9 @@ if have_tmux; then
window_name="$(tmux display-message -p '#W' 2>/dev/null || :)"
win_harness="$(tmux display-message -p '#{session_name}:#{window_id}' 2>/dev/null || :)"
fi
[ -n "$win" ] || win="${WORKBENCHES_CLAUDE_WINDOW_REF:-}"
[ -n "$win" ] || win="${OPENBENCHES_CLAUDE_WINDOW_REF:-${WORKBENCHES_CLAUDE_WINDOW_REF:-}}"
window_ref_ok "$win" || win=""
[ -n "$wid" ] || wid="${WORKBENCHES_CLAUDE_WINDOW_ID:-}"
[ -n "$wid" ] || wid="${OPENBENCHES_CLAUDE_WINDOW_ID:-${WORKBENCHES_CLAUDE_WINDOW_ID:-}}"
window_id_ok "$wid" || wid=""
window_ref="${win}${wid:+ $wid}"

Expand Down Expand Up @@ -1866,7 +1903,7 @@ if [ -z "$dir" ]; then
*) dir=""; dir_read_failed="\`$LANES_EDIT lane-dir $lane\` failed (exit $dir_rc)" ;;
esac
if [ -z "$dir" ] && [ -z "$dir_read_failed" ]; then
dir="${WORKBENCHES_CLAUDE_LANE_DIR:-}"
dir="${OPENBENCHES_CLAUDE_LANE_DIR:-${WORKBENCHES_CLAUDE_LANE_DIR:-}}"
if [ -z "$dir" ] && [ -n "${CLAUDE_CODE_SESSION_ID:-}" ] && [ -n "${CLAUDE_CONFIG_DIR:-}" ] && command -v jq >/dev/null 2>&1; then
dir="$(jq -r --arg id "$CLAUDE_CODE_SESSION_ID" \
'select((.sessionId // "") == $id) | .cwd // empty' \
Expand Down
59 changes: 54 additions & 5 deletions skills/handoff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,53 @@ case "${why-}" in
esac
```

**THE LAUNCHER'S ENVIRONMENT IS NEW-FIRST WITH LEGACY FALLBACK, AND A DUAL CONFLICT IS A REFUSAL.** For
`WINDOW_REF`, `WINDOW_ID` and `LANE_DIR`, step 4 reads `OPENBENCHES_CLAUDE_*` first and then the corresponding
`WORKBENCHES_CLAUDE_*` name. A value supplied under only one name is used as before; equal old/new dual values
are accepted. If both names are set to different values, the skill refuses before lane selection, window
rename, handoff refresh or record writes rather than risking the wrong lane or checkout. Set both names to the
same value, or unset the incorrect one, and run `/handoff` again. A set-but-empty value still counts as set,
so an empty/non-empty pair conflicts too. These checks run now, ahead of step 1, because the directory and
window reads in step 4 are too late to protect step 2's writes.

```sh
check_claude_env_dual() { # <WINDOW_REF|WINDOW_ID|LANE_DIR>
local cce_suffix="$1" cce_new_set cce_legacy_set cce_new cce_legacy
case "$cce_suffix" in
WINDOW_REF)
cce_new_set="${OPENBENCHES_CLAUDE_WINDOW_REF+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_WINDOW_REF+set}"
cce_new="${OPENBENCHES_CLAUDE_WINDOW_REF-}"
cce_legacy="${WORKBENCHES_CLAUDE_WINDOW_REF-}"
;;
WINDOW_ID)
cce_new_set="${OPENBENCHES_CLAUDE_WINDOW_ID+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_WINDOW_ID+set}"
cce_new="${OPENBENCHES_CLAUDE_WINDOW_ID-}"
cce_legacy="${WORKBENCHES_CLAUDE_WINDOW_ID-}"
;;
LANE_DIR)
cce_new_set="${OPENBENCHES_CLAUDE_LANE_DIR+set}"
cce_legacy_set="${WORKBENCHES_CLAUDE_LANE_DIR+set}"
cce_new="${OPENBENCHES_CLAUDE_LANE_DIR-}"
cce_legacy="${WORKBENCHES_CLAUDE_LANE_DIR-}"
;;
*)
printf 'REFUSED: unknown Claude environment suffix: %s\n' "$cce_suffix" >&2
return 2
;;
esac
if [ "$cce_new_set" = set ] && [ "$cce_legacy_set" = set ] && [ "$cce_new" != "$cce_legacy" ]; then
printf 'REFUSED: OPENBENCHES_CLAUDE_%s and WORKBENCHES_CLAUDE_%s have conflicting values. Set both to the same value, or unset the incorrect one, then re-run. Nothing was renamed or written.\n' \
"$cce_suffix" "$cce_suffix" >&2
return 2
fi
}
check_claude_env_dual WINDOW_REF || exit
check_claude_env_dual WINDOW_ID || exit
check_claude_env_dual LANE_DIR || exit
```

## 1. Usage, then the identity triple — derived, not asked

```bash
Expand Down Expand Up @@ -542,7 +589,8 @@ below, and never a template placeholder (#80 item 7).
# 1. the lane's own recorded `dir` (`lane-dir`), written by `lane-start` at
# every start under this amendment — the lane's checkout as the lane's own
# writer recorded it;
# 2. the launcher's `WORKBENCHES_CLAUDE_LANE_DIR`, exported only where the
# 2. the launcher's `OPENBENCHES_CLAUDE_LANE_DIR`, with legacy
# `WORKBENCHES_CLAUDE_LANE_DIR` fallback, exported only where the
# directory order's rungs 1-3 answered;
# 3. THE LIVE SESSION'S OWN RECORD — the harness writes `"cwd"` beside
# `"tmux"` in `$CLAUDE_CONFIG_DIR/sessions/<pid>.json`, keyed by
Expand Down Expand Up @@ -580,7 +628,7 @@ case "$dir_rc" in
*) dir=""; dir_read_failed="\`$L lane-dir $lane\` failed (exit $dir_rc)" ;;
esac
if [[ -z "$dir" && -z "$dir_read_failed" ]]; then
dir="${WORKBENCHES_CLAUDE_LANE_DIR:-}"
dir="${OPENBENCHES_CLAUDE_LANE_DIR:-${WORKBENCHES_CLAUDE_LANE_DIR:-}}"
if [[ -z "$dir" && -n "${CLAUDE_CODE_SESSION_ID:-}" && -n "${CLAUDE_CONFIG_DIR:-}" ]]; then
dir="$(jq -r --arg id "$CLAUDE_CODE_SESSION_ID" \
'select((.sessionId // "") == $id) | .cwd // empty' \
Expand All @@ -595,7 +643,8 @@ fi
# SUPERSET, so a seventh divergence found after this is absorbed on the same
# ground and needs no ruling of its own"*. On the normal path the launcher
# started this session and threaded the window it captured across its re-exec
# (`WORKBENCHES_CLAUDE_WINDOW_REF`, `_WINDOW_ID`, clause (b) act 1); a `tmux`
# (`OPENBENCHES_CLAUDE_WINDOW_REF`, `_WINDOW_ID`, with the legacy
# `WORKBENCHES_CLAUDE_*` names as fallback, clause (b) act 1); a `tmux`
# that is missing, a server that has gone, or a shim that answers nothing then
# costs the record a `window` sub-field it already had in hand. Each is SHAPE
# CHECKED before it is taken, by the same two tests the live reads get, because
Expand All @@ -614,11 +663,11 @@ fi
win="$(tmux display-message -p '#S:#I' 2>/dev/null)"
[[ "$win" =~ ^.+:[0-9]+$ ]] || win=""
case "$win" in *$'\n'*|*$'\r'*) win="" ;; esac
[[ -n "$win" ]] || win="${WORKBENCHES_CLAUDE_WINDOW_REF:-}"
[[ -n "$win" ]] || win="${OPENBENCHES_CLAUDE_WINDOW_REF:-${WORKBENCHES_CLAUDE_WINDOW_REF:-}}"
[[ "$win" =~ ^.+:[0-9]+$ ]] || win=""
case "$win" in *$'\n'*|*$'\r'*) win="" ;; esac
wid="$(tmux display-message -p '#{window_id}' 2>/dev/null)"
[[ "$wid" =~ ^@[0-9]+$ ]] || wid="${WORKBENCHES_CLAUDE_WINDOW_ID:-}"
[[ "$wid" =~ ^@[0-9]+$ ]] || wid="${OPENBENCHES_CLAUDE_WINDOW_ID:-${WORKBENCHES_CLAUDE_WINDOW_ID:-}}"
# AN `<@id>` IS `@<digits>` AND NOTHING ELSE (F-X13(a), A11 Addendum 4 ruling 10).
# `== @*` accepted anything starting with an at-sign, and two things that are
# not ids do: a tmux too old to know `#{window_id}` PRINTS THE FORMAT BACK, and
Expand Down
Loading
Loading