Skip to content

v0.18.1 — a third pane on a fresh start, duplicate spaces, and `og agents` dying on a 404

Choose a tag to compare

@jmvbambico jmvbambico released this 15 Oct 09:28
· 121 commits to main since this release
004225c

Fixed

  • A fresh og start herdr no longer opens a third pane. Two halves were doing one job: _open_space types the chat command into the pane it is about to split, and since v0.18.0 og chat splits the pane it finds itself in — so the process pane_run starts runs the very code that splits beside it. The shared split_agents_pane guarded nothing; its known_panes argument only ever identified the new pane afterwards.

    The fix is at the og chat end. Deleting the launcher's split was the other candidate and is worse: the launcher is the half that still holds the pane when the chat command never reaches its own, and the half whose degradation reaches you before the terminal is handed to herdr. A guard keyed on known_panes is not possible either — pane.list returns only pane_id, tab_id and workspace_id, so "this tab already holds the og agents pane" and "this tab holds a pane you split yourself" are the same answer, and declining on the second is how og chat in a space you made by hand would silently lose the pane known_panes exists to protect.

    So the question asked is one og can answer: did og open this pane? og_owns_pane looks for a record naming this workspace, tab and pane. Both would-be splitters write one before they act, so it is ordered rather than raced, and both state buckets are read because a claim moves the record from pending to spaces unchanged. All three ids must match exactly — a record with no tab_id matching every tab would stand down in a tab the launcher never touched and cost you the pane. It fails open: an unreadable state file is og having nothing to say, not og saying "mine".

  • The bridge no longer opens duplicate spaces for one directory. og start launches the bridge before exec'ing the launcher, and the bridge loaded its state once at construction — so the launcher's pending record landed in a file it had already read, the dedupe missed, and a second space opened for a directory that already had one. Worse than a stale read: _prune_pending saves the whole file, so a prune against the startup snapshot deleted the launcher's record, and a record no process holds can never be found by a later pass. That is why the duplicate never healed.

    The bridge now re-reads at each reconcile pass, ahead of the prune, and merges per key against the last state it knew the file held — a key it has not changed belongs to whoever wrote the file last, including a writer who replaced it. A claim is a completed operation, not an unsaved edit, and is no longer mistaken for one.

  • The two writers of og-herdr.json take a lock. Every interesting operation on that file is a read-modify-write, and re-reading immediately before writing narrows the window without closing it — a second read only moves the race to the other side of itself. state_update now does load → decide → write as one transaction under an advisory fcntl.flock, and og chat's own pane record shares it. The lock lives on an og-herdr.json.lock sidecar, never the state file: the writer finishes with os.replace, which swaps the inode, so a lock taken on the file itself would leave two holders locking different inodes and excluding nobody. Advisory, so a writer that ignores it is still last-writer-wins; where fcntl is absent the behaviour is exactly what it was.

  • og agents talks to og's own server, and degrades instead of dying. cmd_agents was the one caller that never forwarded --server, so on an install whose server moved off 6767 it queried the default port and read an unrelated listener's 404. Every sibling already forwarded it. The viewer also died on any non-200 — fetch_listing was the single escaping wire call, every detail fetch already being guarded — tearing down curses with a raw traceback. It now keeps the last good rows behind a footer notice naming the server and status, and heals when the server returns.

  • og start no longer types an attach for a session on another machine. A session for the same directory living on a different host matched workspace == cwd and won, and the attach died with "its host is offline". runner_is_offline cannot catch it: a session on another machine has no offline runner here at all. A session whose host is known and different is now skipped.

  • The host rule above could never actually fire. host_id was not among the fields merged onto the session row the rule tested, so the check read None every time and was dead code. It is forwarded now, and this machine's host id comes from the daemon record first, falling back to /v1/hosts only when exactly one host is online — never "the first online host", which on a multi-host account is a guess.

  • A --dry-run claim consumes nothing, in memory either. It previously moved the record out of pending and declined only the disk write, leaving memory a key short with no save to reconcile it — which the new merge rule then reads as a completed claim, permanently. A run that changes nothing could durably remove a real launcher record from the bridge's view.

Docs

  • docs/MUX-LAUNCHER.md §2 gains the pane-ownership rule and the pane.list limitation that forces it; the "two things open the agents pane" note was stale since v0.18.0 and now names three.