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
35 changes: 34 additions & 1 deletion lane-handoff
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,24 @@ while [ $# -gt 0 ]; do
esac
done

# THE WHY IS ONE LINE, AND IT IS REFUSED AT THE DOOR (opensoft/openRepoTools#72
# item 6, #85). It reaches FOUR places, and only the last two pass through
# `sanitize`: the handoff's top block, where a newline writes a malformed prompt
# (a `---` line among them, which is where `lane-start`'s `handoff_top_block`
# ends the first prompt it hands the next session); the handoff commit's
# subject; the object log's PAUSED line, where `write_event` refuses it, but
# only AFTER section 2 has refreshed and committed the handoff, so the act ends
# with a top block and no record behind it; and the register's file-level
# line, where `append-line` used to write it first and refuse it after. One
# test here covers all four, and it comes before the identity triple, the
# refresh and every write, so the refusal leaves nothing to undo. It is the
# rule `lane-end --reason` and `rename-lane`'s reason already keep for a free
# text that becomes one line. `sanitize` flattens a newline as well, but that
# is the second line of defence and not this one.
if [ "${why//[$'\n\r']/}" != "$why" ]; then
die "the why is ONE line: it is written into the handoff's top block, its commit message, the object log's PAUSED line and the register's file-level line, and a newline or carriage return in it would split each of them — the last two in files nothing rewrites. Nothing was written. Put it on one line, with '; ' where the break was, and re-run." 2
fi

# ONE ACT AT A TIME AFTER THE RECORD. `--restart` respawns the pane for a lane
# that STAYS HERE and `--exit` ends the session for a lane going somewhere else
# (Amendment 18(d)); asking for both is asking the same pane to do two opposite
Expand Down Expand Up @@ -2150,7 +2168,22 @@ fi
# THE FREE TEXT IS SANITIZED ONCE: Amendment 7(b)/R26 refuses any payload or
# free text containing ` — ` (exit 2), because that separator is what divides a
# line's fields from its free text.
sanitize() { printf '%s' "$1" | sed 's/ — /; /g'; }
#
# AND A LINE BREAK IS SPELLED `; ` TOO (opensoft/openRepoTools#85), whatever
# its spelling — CRLF, a lone CR, a lone LF — so that one break is one `; `.
# Both writers this text reaches refuse a newline before they write
# (`write_event` for the log, `append-line` for the register), and a why that
# carries one is refused at the door, right after the arguments are read, so
# nothing that reaches here today carries one. This is the second line of
# defence, in the shape #85 asks of the caller: where a break does arrive, the
# act still happens, on one line. `sed` cannot do it, because `sed` reads a
# line at a time and hands the break back.
sanitize() {
sz="${1//$'\r\n'/; }"
sz="${sz//$'\r'/; }"
sz="${sz//$'\n'/; }"
printf '%s' "$sz" | sed 's/ — /; /g'
}
# THE FREE TEXT NAMES THE KIND AFTER ITS WHY (Amendment 17 Addendum 1 (h), in
# force 2026-09-14T20:59:31Z): `clear in-process`, `clear respawn`, `switch
# respawn`, `reset respawn` — the why the caller gave, then the word for what
Expand Down
16 changes: 15 additions & 1 deletion lane-start
Original file line number Diff line number Diff line change
Expand Up @@ -4032,9 +4032,23 @@ fi
# writes this line and the handoff's stamp, both carrying the real uuid.
# (`lane_sid_known` is computed with the launch decision above, which is where
# `lane_sid` is decided and where the row's own stamp first reads it.)
#
# AND THE COMMAND IT PRINTS CARRIES THE AGENT THIS RUN WAS GIVEN
# (opensoft/openRepoTools#72 item 5). `$self_args` is the lane and nothing
# else, so a lane started `--agent codex` — which never has a uuid here, so it
# ALWAYS reaches this line — was handed a first act that dropped the flag, and
# the person who ran it had `lane-agent` choose instead: the agent of the lane's
# last PAUSED, which is whichever agent paused it and not the one now running
# it. `AGENT_EXPLICIT` is 1 for an agent named on the command line and for one
# the record named that is not the default; either way the run that settles
# this one is the same agent's, so it is written down, quoted like every other
# command this file prints. It goes AFTER the lane's own arguments, so the
# `--no-launch <repo> <n>` a reader looks for is still one run of words.
ls_defer_agent=""
if (( AGENT_EXPLICIT )); then ls_defer_agent=" $(quoted --agent "$AGENT")"; fi
if (( ! lane_sid_known )); then
note "no transcript uuid is knowable at this instant (the row's stamp says '${lane_sid:-unknown}'), so the lane's $lane_verb log line is NOT written: Amendment 7(b) makes that field a transcript uuid and only that, and the log is append-only. Nor is the handoff's Rule 3 stamp. BOTH are written by the first act that knows the uuid, which is this session's own:"
note " $prog --no-launch $self_args"
note " $prog --no-launch $self_args$ls_defer_agent"
elif (( dry_run )); then
plan "lanes-edit.sh log $lane_verb lane:$LANE → \"$lane_payload\""
else
Expand Down
30 changes: 30 additions & 0 deletions lanes-edit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,25 @@ delete_lines() { # <line numbers, one per line, any order>
# more, not one existing byte different.
append_text_line() {
newline="$1"; target="${2:-$LANES_FILE}"
# ONE LINE IS REFUSED A NEWLINE BEFORE IT IS WRITTEN, NOT PROVED ONE AFTER
# (opensoft/openRepoTools#85). The proofs below are made AFTER the `>>`, so a
# text carrying a newline was appended whole — two physical lines where one
# was meant — and only then refused: `append changed line count by 2`, exit
# 5, the file left modified and uncommitted. What came next was worse than
# the refusal. A dirty object log is refused by every later writer in that
# checkout; a dirty REGISTER is committed by the next one, as a peer's
# `pre-existing` edit (`handle_preexisting`) — measured in `lane-handoff`'s own
# flow, where the row flip right after the refused PAUSED line committed the
# two lines. A carriage return is refused with it: `wc -l` does not count one, so it
# passed the proof and put a line no reader splits the same way into a file
# nothing rewrites. `write_event` has refused both since R21; this is the
# same gate in the one function every append goes through, which is the
# reason this file gives for putting the container guard at the dispatcher:
# *"nine copies of one rule is how eight of them would come to disagree"*.
# FIRST, before the `mktemp` and the copy, so a refusal touches nothing.
if [ "${newline//[$'\n\r']/}" != "$newline" ]; then
die "an appended line is ONE line, and this text carries a newline or a carriage return: appended, it would put several lines into $target where one was meant, and the proof that only one was added fails after the write, not before it. Nothing was written. Spell the break '; ' and re-run." 2
fi
TMPD="$(mktemp -d)"
pre="$TMPD/pre"
cat -- "$target" > "$pre"
Expand Down Expand Up @@ -13610,6 +13629,17 @@ Nothing was written." 2

append-line)
text="${1-}"; [ -n "$text" ] || die "usage: append-line \"<text>\"" 2
# ONE LINE, REFUSED A NEWLINE BEFORE THE LOCK (opensoft/openRepoTools#85).
# `append_text_line` refuses it too, and before it writes — but that is
# after `acquire_lock` and `handle_preexisting`, which can COMMIT a peer's
# uncommitted register edit before this act's own write. A refusal that has
# already committed something is a refusal that wrote, so the verb asks
# first, exactly where `write_event` asks for `log`: the checkout is left
# as this command found it. `lane-handoff`'s file-level PAUSED line is the
# caller that met it, with a `why` carrying a newline.
if [ "${text//[$'\n\r']/}" != "$text" ]; then
die "append-line appends ONE line to the register, and this text carries a newline or a carriage return: it would append several, and the proof that only one was added would fail after the write, leaving the register modified and uncommitted. Nothing was written, and the lock was not taken. Spell the break '; ' and re-run." 2
fi
# NO MARKER BY THE BACK DOOR (Copilot round 3 on #97): `add-row` refuses a
# row in the marker's vocabulary, and this appends any line at all to the
# register — a row-shaped one included.
Expand Down
85 changes: 85 additions & 0 deletions tests/test_lane_helpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9566,6 +9566,69 @@ is "…and nothing was written by any of the three" "$(grep -c '^PAUSED' "$LO
# `PAUSED` lines on purpose: section 3 below is the `--late` rule, and its whole
# question is what a lane with no paused record does.)

# A ONE-LINE RECORD CANNOT TAKE A NEWLINE, AND IT IS REFUSED BEFORE ANYTHING IS
# WRITTEN (opensoft/openRepoTools#85, #72 item 6). `append-line` used to append
# its text and only THEN prove it was one line, so a text carrying a newline
# left two lines in the register, the checkout dirty, and a refusal (exit 5)
# that came after the write. And `lane-handoff` put its `why` into four places
# with only two of them sanitized: a `why` carrying a newline refreshed and
# committed the handoff, had its PAUSED refused by `write_event`, handed
# `append-line` two lines — which the row flip after it then committed as a
# peer's `pre-existing` edit — and exited 0. Every case here is asked on
# repoHF-7, whose log is kept EMPTY of `PAUSED` lines for section 3: a refusal
# writes nothing, and that is exactly what these assert.
a17_nl_head="$(git -C "$WIP" rev-parse HEAD)"
a17_nl_status="$(git -C "$WIP" status --porcelain)"
cp -- "$LANES" "$SANDBOX/a17-nl-lanes.md"
cp -- "$WIP/handoffs/repoHF/repoHF-7.md" "$SANDBOX/a17-nl-handoff.md"
cp -- "$LOGD/repoHF-7.md" "$SANDBOX/a17-nl-log.md"
a17_nl_same() { # <file> <its copy> — `same` where not one byte moved
if cmp -s -- "$1" "$2"; then printf same; else printf changed; fi
}
run env LANES_LANE=repoHF-7 "$E" append-line \
"$(printf 'PAUSED — lane repoHF-7, session %s@Eagle, 2026-09-14T09:30:00Z, clear\nsecond line; handoff refreshed' "$HF4_ID")"
is "append-line refuses a text carrying a newline" "$rc" 2
has "…saying it appends ONE line" "$err" "append-line appends ONE line"
is "…before it writes: the register is byte for byte as it was" \
"$(a17_nl_same "$LANES" "$SANDBOX/a17-nl-lanes.md")" same
is "…and the checkout is as it was, so the next writer in it is not refused for a dirty register" \
"$(git -C "$WIP" status --porcelain)" "$a17_nl_status"
run env LANES_LANE=repoHF-7 "$E" append-line "$(printf 'LANDING — lane repoHF-7, a carriage\rreturn')"
is "…and a carriage return the same way, which the line count after the write never saw" "$rc" 2
is "…with nothing written for it either" "$(a17_nl_same "$LANES" "$SANDBOX/a17-nl-lanes.md")" same

# AND THE GATE IS IN `append_text_line` ITSELF, so every caller has it — `add-row`,
# `log`'s Rule 6 line, a rename's lines — and not only the verb that met it.
# Lifted out of the helper the way the `cut_to_line` cases below lift theirs,
# and asked against a file of its own.
printf 'line one\n' > "$SANDBOX/a17-nl-atl.txt"
run bash -c 'eval "$(sed -n -e "/^die()/,/^}/p" -e "/^note()/p" -e "/^append_text_line()/,/^}/p" "$1")"; append_text_line "$2" "$3"' \
_ "$E" "$(printf 'two\nthree')" "$SANDBOX/a17-nl-atl.txt"
is "append_text_line refuses a newline itself, whichever verb called it" "$rc" 2
is "…before its copy and its \`>>\`: the file is still its one line" "$(cat "$SANDBOX/a17-nl-atl.txt")" "line one"

run env PATH="$A17PATH" CLAUDE_CODE_SESSION_ID="$HF4_ID" CLAUDE_PROFILE_NAME=team-05a \
"$HANDOFF_CMD" --lane repoHF-7 "$(printf 'clear\nsecond line')"
is "lane-handoff refuses a why carrying a newline" "$rc" 2
has "…at the door, saying the why is one line" "$err" "the why is ONE line"
is "…the handoff not refreshed" \
"$(a17_nl_same "$WIP/handoffs/repoHF/repoHF-7.md" "$SANDBOX/a17-nl-handoff.md")" same
is "…the object log not written" "$(a17_nl_same "$LOGD/repoHF-7.md" "$SANDBOX/a17-nl-log.md")" same
is "…the register byte for byte as it was" "$(a17_nl_same "$LANES" "$SANDBOX/a17-nl-lanes.md")" same
is "…nothing committed" "$(git -C "$WIP" rev-parse HEAD)" "$a17_nl_head"
is "…and the checkout as it was" "$(git -C "$WIP" status --porcelain)" "$a17_nl_status"
run env PATH="$A17PATH" CLAUDE_CODE_SESSION_ID="$HF4_ID" CLAUDE_PROFILE_NAME=team-05a \
"$HANDOFF_CMD" --lane repoHF-7 "$(printf 'clear\r')"
is "…and a carriage return is refused the same way" "$rc" 2

# `sanitize` IS THE SECOND LINE OF DEFENCE: a break that reaches it is spelled
# `; `, one per break whatever its spelling, so the act still happens on one
# line. Lifted out of `lane-handoff` in a shell of its own, because this file
# has a `sanitize` of its own (the transcript directory's).
run bash -c 'eval "$(sed -n "/^sanitize()/,/^}/p" "$1")"; sanitize "$2"' \
_ "$HANDOFF_CMD" "$(printf 'a\r\nb\rc\nd — e')"
is "lane-handoff's sanitize spells every line break '; ', as it does ' — '" "$out" "a; b; c; d; e"

# `--utc` IS THE LATE `PAUSED`'s SEAM AND HAS NO OTHER CALLER (Amendment 17,
# adoption act 7). This log is append-only and FILE ORDER is what every state
# read means by "last", so a STARTED or a RESUMED dated by hand changes what
Expand Down Expand Up @@ -9989,6 +10052,15 @@ has "…the stamp naming the agent and its own id" \
has "…the row's session cell appended in the agent's own spelling" \
"$(grep '^| `repoAG-1`' "$LANES")" "→ Codex \`$CODEX_ID\`"
has "…and the row's stamp says who resumed it" "$(grep '^| `repoAG-1`' "$LANES")" "RESUMED by codex $CODEX_ID (lane repoAG-1)"
# THE FIRST ACT IT DEFERS KEEPS THE AGENT (opensoft/openRepoTools#72 item 5).
# `codex` keeps no id this command can pass, so a codex launch ALWAYS defers its
# log line and its Rule 3 stamp to the session's own `--no-launch` — and the
# command printed for that act dropped `--agent`, leaving `lane-agent` to choose:
# the agent of the lane's last PAUSED, not the one now running it.
ag_deferred() { # <stderr> — the one command lane-start prints for the deferred records
printf '%s\n' "$1" | awk 'f { print; exit } /which is this session.s own:$/ { f = 1 }'
}
has "…and the first act it defers to the session carries --agent codex" "$(ag_deferred "$err")" " --agent codex"

: > "$FAKE_CODEX_LOG"
export FAKE_CODEX_WATCH="$WIP/handoffs/repoAG/repoAG-2.md"
Expand Down Expand Up @@ -10047,6 +10119,19 @@ has "…saying whose the recorded transcript is, and that this launch does not
"$err" "names agent claude and this launch is codex"
hasnt "…and the session cell is NOT appended with a Claude uuid in Codex's spelling" \
"$(grep '^| `repoAG-4`' "$LANES")" "Codex \`$HF2_ID\`"
# THE CASE #72 ITEM 5 IS ABOUT: the record names claude and the person named
# codex, so a deferred act without the flag would be settled by claude.
is "…and its deferred first act names codex, which this lane's record would not" \
"$(ag_deferred "$err")" "lane-start: lane-start --no-launch repoAG 4 --agent codex"
# AND A RUN THAT NAMED NO AGENT, OVER A RECORD THAT NAMES NONE, PRINTS NONE:
# the flag is written down where this run's agent was explicit and only there,
# so the deferred act chooses its agent exactly as this run did. repoAG-10 has
# no row and no log, and with nothing minted its records are deferred too.
run env PATH="$A17PATH" FAKE_TMUX_WINDOW="agsess:@41" CLAUDE_PROFILE_NAME=team-05a LANE_START_SESSION_ID=0 \
"$START" --dir "$AG_DIR" repoAG-10 --no-launch
is "a default-agent start with no uuid to name exits 0" "$rc" 0
has "…deferring its records to the session's own first act" "$(ag_deferred "$err")" "--no-launch repoAG 10"
hasnt "…and naming no agent in it" "$(ag_deferred "$err")" "--agent"

# A LANE WITH NO ROW IS A `STARTED`, WHATEVER BRANCH LAUNCHED IT. `--agent` and
# the `/ctx` seam both set the verb to RESUMED because they resume a lane that
Expand Down
Loading