fix: session isolation + await cursor + cleanup#1
Conversation
Add global rooms (-g flag, ~/.chatfiles/), delete-room command, auto-resolution of .Chatfile extension, and install.sh reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5 confirmed bugs: 1. register crashes on first name collision (set -e + ((attempts++))) 2. await returns join/leave messages instead of waiting for new ones 3. await returns already-seen messages (no read cursor) 4. Newline injection in send breaks one-message-one-line invariant 5. Uniqueness check ignores join/leave format names Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. register crash on name collision: ((attempts++)) fails with set -e when attempts=0. Fixed with || true and added exhaustion error exit. 2. await returns join/leave lines: cut -d: misparses non-message format. Replaced with line cursor tracking that skips [...] lines. 3. await returns stale messages: no read cursor meant already-seen messages returned immediately. Added LASTLINE tracking to session. 4. newline injection in send: embedded newlines broke one-line invariant. Strip newlines from messages before appending. 5. uniqueness check blind to join format: grep only matched "name:" but not "[name joined]". Extended pattern to match both formats. Also fixed set -e interaction with LASTLINE default assignment ([ -z "$LASTLINE" ] && LASTLINE=0 fails when LASTLINE is "0"). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug reproduction tests should demonstrate the underlying vulnerability, not test the fix. The test proves grep "^name:" is blind to join format, which documents why the fix was needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes tail -f orphan leak and await not returning messages: - Use exec fd < <(tail -f) pattern instead of pipe subshell - tail PID captured via $! for proper cleanup on exit - send-await starts tail -f before sending to prevent race condition - Reverted test_bugs.sh bug5 to original form (tests the concept) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Since state was previously stored in a single '.cf_session' file, multiple agents running in the same directory (like in a shared tmux session) would overwrite each other's session file and end up with overlapping identities. This replaces the hardcoded '.cf_session' with a dynamic TTY-based filename (e.g., '.cf_session.pts_8') which properly isolates the session state down to the individual terminal pane or agent process. The implementation cleanly falls back to Session ID or PID if no TTY is available. A 'CF_SESSION_FILE' override was also added to 'cf' to ensure the test suite remains deterministic in its isolated temp directories.
- Fixed Bug 6 (Data Loss in await): When unread messages were processed, await was incorrectly setting LASTLINE to TOTAL instead of the line number of the specific message it just read, causing it to skip all subsequent unread messages on the next call. It now accurately tracks the exact line. - Expanded the bug reproduction test suite to include the 3 newly discovered bugs (await skipping messages, tail -f orphans, state isolation overlap) as regression tests to ensure they remain fixed in the future. - Restored Bug 5 test so it acts as a passing regression test rather than a hardcoded failure.
get_session_id() was using the script's own SID, which changes every invocation when there's no controlling terminal (agents, subshells). Use PPID instead — stable across all commands from the same parent shell. Also removes scratch files (sketch.py, PROMPT.md) from the branch. Closes #2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Using wc -l to update the cursor during simultaneous sends caused messages to be permanently skipped. Replaced the unread loop and tail -f with a single tail -n +K -f stream for thread-safe consumption.
|
Reviewed during hivemind triage. Status: Tests still green on the PR branch as-is:
But the PR has rotted and now conflicts with main:
Unique-to-#1 work that didn't make it into #3/#4 (from the body):
A rebase needs hand-resolving the |
|
Closing as superseded — this can no longer merge cleanly and would regress Since this PR was opened (2026-03-05),
As a result this PR's diff vs current Salvaged the one unambiguous, still-applicable fix → #7: the Still-valid follow-up candidates from this PR, for whoever picks them up (each is independent and small):
Thanks — the diagnostics here were good; closing in favor of the rebased salvage. |
… set -e (#7) `cf register`'s retry loop used `((attempts++))`. Post-increment evaluates to the *old* value, so on the first iteration it yields 0 — which bash reports as exit status 1. Combined with the script's `set -e`, that aborted `cf register` entirely the moment a generated name collided with an existing one, instead of retrying. Replace it with `attempts=$((attempts + 1))` (an assignment always returns success), and add a guard so exhausting all 100 attempts errors out rather than silently registering a duplicate name. Adds a regression test (`test_register_survives_name_collisions`) that fills a chunk of the name space so collisions are frequent and registers 30×; it fails against the old `((attempts++))` and passes with the fix. Suite: 40/40. Salvaged from the superseded #1 (which can't merge — it predates the global-room rework and would revert main's CI + presentation tests). Co-authored-by: marksverdhei <marksverd@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…riant) (#8) `cf send "$msg"` wrote the arg verbatim, so a message containing a newline landed as two physical lines. The chatroom is line-oriented ("Format: Name: msg") and `await` derives the sender via `tail -n 1 | cut -d: -f1`, so the wrapped second line — which has no "Name:" prefix — gets parsed as a phantom message from a sender named after the text. Collapse newlines to spaces in both `send` and `send-await`. Adds `test_send_collapses_newlines` (a 2-line message must add exactly one physical line, prefixed by the sender). Fails against the old code, passes with the fix; suite 41/41. Second of the follow-ups noted when #1 was closed as superseded. Co-authored-by: marksverdhei <marksverd@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
get_session_id()now usesPPID(parent shell PID) instead of own SID when no TTY is available — session state persists acrosscfsubcommands in non-TTY contexts (agents, subshells,bash -c)awaitdoesn't return stale/already-seen messagessendto preserve one-message-one-line invariant[name joined]format((attempts++))crash underset -eon first collisionawait/send-awaitto prevent orphanedtail -fprocessesdelete-roomcommand, global room support (-g), auto-resolve.Chatfileextensiontest_bugs.shregression tests (8 bug scenarios, all pass)sketch.py,PROMPT.md)Test plan
test_cf.sh— 32/32 passtest_bugs.sh— 0/8 bugs reproduced (all fixed)🤖 Generated with Claude Code