Skip to content

Close the remaining product gaps: one socket bridge, Antigravity dialogs, encrypted keys, accessibility - #177

Merged
cobanov merged 19 commits into
mainfrom
remaining-gaps
Oct 26, 2026
Merged

cobanov merged 19 commits into
mainfrom
remaining-gaps

Conversation

@woosal1337

@woosal1337 woosal1337 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Rebased onto main at 2ce4a9a (#178), with the changes asked for in review. Commit ids below are the rebased ones.

The product gaps left after #172 to #176, one commit each: a faster socket path, Antigravity dialogs, encrypted keys, accessibility, and a set of correctness debts from the audit. This branch sits on hc-107-visual-fixes (#176); merge it after #176.

Changes

Speed

  • 2a39e42 One socket bridge per connection (HC-95). Every socket request opened an SSH channel and started python3 on the host. The python bridge now runs once per connection: requests go in on its stdin with a tag, and answers come back on stdout with the same tag. Each request gets its own socket connection on its own thread, so a waiting agent.prompt holds up no read. Against herdr 0.9.5 on this Mac, three requests at once came back out of order in about 17 ms each.
    • The native modules gain writeStream. On iOS it uses SwiftNIO's TTY stdin writer, which NativeChecks round-trips against a private sshd. On Android it uses the exec channel's output stream (compiled by CI only).
    • The exec path stays: where a transport cannot write to a command (the Demo), where the bridge cannot start, and after three bridges in a row end without one answer.
    • A timeout is the transport's (timeout), since nobody knows if the request landed. The request size limit holds on both paths.
  • cf1a999 Pictures stripped from the live tail on the host (HC-89). The tail is tail -c +N -f through LC_ALL=C awk, which prints each line's byte count before it, so the cursor stays exact in UTF-8 bytes. The tests run the real sh and awk.
  • e2be62e The list takes the open chat's snapshot on iPad (HC-91): 50 to 30 snapshots a minute with a chat open beside the list.

Agents

  • b9ac497 Antigravity dialogs from the phone (HC-109). Measured on agy 1.3.6: a digit alone answers permissions and questions; the trust menu is unnumbered, so Enter or Down then Enter. The parser joins wrapped labels, reads the permission details, and names the agent from "X requires permission to". 35 built-in commands go to the palette. Fixtures are real agy screens with the paths renamed.

Correctness and security

  • cfc2f94 Encrypted private keys (HC-7). The host form asks for a passphrase, kept in the keychain beside the key. The docs say which key types work where: ed25519 and RSA on both platforms, ECDSA on Android only.
  • ae566b8 Small debts (HC-23). Stop now checks that the agent left working and says so if it did not (interrupt_unconfirmed), sending Escape once. The host's herdr version is kept per connection. A context of 0 shows nothing.
  • e661f62 Offline, no recycled slot's chat (HC-18). Opened from the list offline, a chat shows its saved messages only when the list saw the same session.
  • a1a601f Tests for the host-key pin (HC-5): an empty key is never pinned, and a nameless key is refused.
  • 141215c Dead code (HC-31): the previews table nothing wrote (dropped) and the diagnostics slot nothing filled.
  • 149eed0 Save says why it failed (HC-119). On a simulator build without keychain entitlements, the connection test passed and Save then did nothing: the keychain refused the write and the press handler's rejection went nowhere. The editor now stays open and says what failed.
  • 1e0216e A Windows host is told to use bash (HC-118). A Windows computer reached over SSH showed cmd's own "'export' is not recognized as an internal or external command," in the list. cmd's and PowerShell's "not recognized" now read as windows_shell, with the fix the guide gives: Git for Windows bash as the OpenSSH DefaultShell.
  • 6a34191 A new chat whose agent stops at a startup question opens (HC-129). A new chat in a folder the agent had not trusted failed in New chat with herdr's own words, "blocked during startup", while the agent kept waiting. herdr answers agent_not_ready and keeps the agent; that now counts as started, and the chat's blocked bar asks the question.
  • c18c12b A new chat's folder is checked first (HC-130). herdr makes a workspace for a folder that does not exist in the home folder instead, without a word: a mistyped path started an agent in ~ on a real host. The folder is now checked on the host (and ~ read as the home folder) before anything is made, and a missing one is refused with dir_missing.

Accessibility (HC-32)

  • 704a431 VoiceOver now hears " needs you: " when the open chat's agent stops to ask, and " replied." when a new reply lands. Text that was in tertiaryLabel (3.78:1 in light mode) moves to secondaryLabel (6.67:1). That covers host key fingerprints, the working timer, the swipe hint and the command hints, and a test keeps tertiary to glyphs. Times and dates follow the device's locale and 12/24-hour setting instead of en-US.

Dropped in review

Tests

  • socketBridge.test.ts: a fake bridge (answers out of order, a batch entry by entry, a timeout, a bridge that ends, three silent bridges, a failed start), and the real python bridge under sh against a Unix socket that answers like herdr (a slow request holds up no other; an emoji survives; a missing socket says herdr is not running).
  • ServerEditScreen.test.tsx (a failed keychain write) and hostErrors.test.ts (three real forms of the Windows message) fail without their fixes.
  • sendPrompt.test.ts (agent_not_ready), hostFolder.test.ts (the folder check under a real sh), clientSocket.test.ts (nothing made for a missing folder) fail without their fixes.
  • announce.test.ts, formatListTime.test.ts, tertiaryText.test.ts for HC-32.
  • Each commit above adds the tests for its own change.

Test plan

  • npx tsc --noEmit: no errors
  • npx expo lint: no errors, no warnings
  • npx jest: 2,064 passed, 14 skipped
  • python3 -m unittest discover -s scripts -p 'test_*.py': passes
  • swift run --package-path modules/herdr-ssh NativeChecks against a private sshd: passes, the stdin round-trip included
  • npm run e2e in Dark and Light on the iPhone 17 Pro simulator (iOS 27), Release build of the rebased stack (Make every agent herdr detects visible: Grok and Antigravity chats, terminal rows for the rest, Codex fixes #172 to Close the remaining product gaps: one socket bridge, Antigravity dialogs, encrypted keys, accessibility #177): 52 of 52, the four flows 5a4cf24 fixes rerun on their own after it. Screenshots opened in both modes.
  • The Android build compiles writeStream: the CI android job passes on this branch now that main (with Make dev builds open again, build native code in CI, run the UI suite nightly #173) is merged in.
  • On the iPhone 17 Pro simulator (iOS 27), against this Mac as a live host through a private sshd: the host test and save, the real chat list, a real Grok chat's history, a new Grok chat from New chat, a message and its reply, and a command run streamed in. One bridge process ran on the host for the whole session and ended with the connection.
  • On the iPhone 18 Pro simulator (iOS 27), live against this Mac:
    • Antigravity with herdr's antigravity-cli integration: New chat opened the chat at agy's trust question, the answer from the app went through, the session was reported, the history and model showed, a permission dialog (pwd) was answered from the app, and the / palette listed agy's commands. The integration was uninstalled after, and agy's config checked against a snapshot.
    • A picture to a new Grok chat: uploaded, shown in the bubble, and read by Grok.
  • On a phone, against a live host

@cobanov

cobanov commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks for this whole stack. We've reviewed #172 to #177 and want to take them, with a few changes.

First, a heads-up: main is about to change. Our branch, shipped as TestFlight builds 87 to 89, goes in shortly. It removes the machine feature entirely: every computer is now its own saved host, and Chats lists every saved host's chats in one list. It also adds Files, Terminal and Computers tabs. It touches many of the files this stack changes (useThread, ThreadScreen, ChatsList, useWorkspaces, connections, client.ts). Since #175 splits those files, could you rebase the stack onto main once it lands and adapt it? I'll post here when main is ready.

Changes we'd like while you do:

Before merging, we'll check the iOS and Android CI builds, run the e2e suite in Dark and Light, and try the socket bridge on our own hosts.

@cobanov

cobanov commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Main is ready: #178 is merged (2ce4a9a). It removes the machine feature and adds the Files, Terminal and Computers tabs. Please rebase the stack (#172 to #177) onto main with the changes from my earlier comment. Thanks!

@woosal1337

Copy link
Copy Markdown
Collaborator Author

Rebased #172 to #177 onto main at 2ce4a9a, with the changes from your comment:

One addition, 5a4cf24 in #172: four of the flows that came with the tabs (composer-keys, terminal, space-layout, list-modes) failed on the longer Demo list in dark and light. A row scrolled near the end sits under the floating tab bar, and a tap there scrolls the list to the top. They now open their rows through the search field, as history and multi-agent already did.

Checked on the rebased stack: tsc, lint, jest and the Python tests on every branch; npm run e2e in dark and light on a Release build (iPhone 17 Pro simulator, iOS 27), 52 of 52 with those four flows rerun after the fix; the CI jobs pass on all five (android and gates on each, ios where it runs: #174, #175, #177). Commit ids in each description are the rebased ones.

@woosal1337 woosal1337 changed the title Close the remaining product gaps: one socket bridge, Gemini and Antigravity, encrypted keys, accessibility Close the remaining product gaps: one socket bridge, Antigravity dialogs, encrypted keys, accessibility Oct 16, 2026

@cobanov cobanov left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the rebase looks right. Everything we asked for is in: no screen chat, no Gemini reader or New chat option, no machine left, and an agent without a reader stays a terminal row. On the rebased stack (main + #180 + #181) we ran tsc, lint, jest and the Python tests, built a Release app and ran npm run e2e in Dark and Light: 52 of 52 (regression/terminal Dark flaked once on "Connected" and passed on a rerun).

#172 is merged (089d3d4), and #174 now targets main. #175 and #176 look good as they are. #174 and this one need a few fixes first (see #174's review for its own), each with a test that would have caught it.

Blocker: the live tail is silent on Debian and Ubuntu hosts (cf1a999, tailScript in src/lib/transcript/store.ts). mawk, the default awk there, buffers what it reads from a pipe, and fflush() flushes only its output. In ubuntu:24.04, tail -c +1 -f f | LC_ALL=C awk '{ print length($0) "\t" $0; fflush() }' printed nothing after a line was appended, not even the first one; with awk -W interactive both lines arrived at once. macOS awk rejects -W, so it has to be chosen per host. The test passed because ShellHost.streamLines drops -f; it should follow the file and append while it reads.

A bridge timeout should reset the connection (2a39e42, LiveBridge.ask in src/lib/herdr/socket.ts). Before, each socket request was an exec, and an exec past its deadline dropped the native client (resetClient()), which is how iOS recovers a half-open socket. The bridge times out on a JS timer only, so on a half-open connection every poll, prompt and Stop waits out its 15 s and fails until TCP gives up, which can take minutes. On a timeout, treat the bridge as dead and let the client reset. Smaller, same file: await live.started isn't bounded by the call's timeout, and after three bridges end unanswered the client stays on exec for good.

Follow-ups from #172, not blocking; a small PR on top is fine:

  • A Codex rewind is still missed while the app stays connected. codexTranscriptPath keeps the path it found for the connection's life and drops it only when the file disappears, which a rewind's old file never does.
  • Antigravity: in a step with several calls, an output after the first is tagged agy-(N+1):0, which no call has, and threadItems falls back only to calls without an id, so the output is dropped. No test has two calls in one step.
  • IntegrationBanner shows the raw integration name ("antigravity-cli integration…").
  • codexBase = source.agent === 'codex' in useThread goes against the new CLAUDE.md rule; it belongs on AgentSpec.
  • The notifier's mtime sort over Codex rollouts can raise if Codex archives one between the search and the sort, and nothing catches it, so the watcher stops.

Heads-up: #182 (Android parity) touches scripts/e2e.sh (a flow list, changed mode, two simulators), HerdrSshModule.kt and the watcher. Whichever lands second will need a rebase; we'll say here.

@cobanov

cobanov commented Oct 21, 2026

Copy link
Copy Markdown
Owner

#182 (Android parity) is merged into main (68f66dc), so the stack needs a rebase onto it when you push the fixes. Expect conflicts in scripts/e2e.sh (flow lists, changed mode, two simulators; regression/grok and regression/agy are already listed there), HerdrSshModule.kt and the watcher. main is at WATCHER_VERSION = 8; if the watcher changes again, make it 9 and re-run node scripts/embed-watcher.mjs.

@cobanov

cobanov commented Oct 21, 2026

Copy link
Copy Markdown
Owner

One more thing on top of the fixes: please take the five #172 follow-ups from the review above as well, in a small PR on top of the stack once it is rebased onto main:

  • Codex rewind missed while connected (codexTranscriptPath keeps the path for the connection's life)
  • Antigravity: outputs after the first call in a step are tagged agy-(N+1):0 and dropped; add a test with two calls in one step
  • IntegrationBanner shows the raw integration name
  • codexBase in useThread belongs on AgentSpec
  • Notifier: the mtime sort over Codex rollouts can raise if one is archived in between, which stops the watcher (bump WATCHER_VERSION to 9)

Each with a test that would have caught it. Thanks!

@cobanov

cobanov commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Checking in on the stack: nothing has been pushed since the reviews of 20.10, so it stays open as it is.

main has moved since #182: #186 (agents bar above the composer) and #187 (the header's tool switch removed) landed today, both in ThreadScreen.tsx and useThread.ts, which #175 splits, and #186 adds regression/agents-pane to scripts/e2e.sh. Please rebase onto main when you push the fixes, and post here when it's ready. Thanks!

A transcript line with a screenshot is about a megabyte, and the live tail
sent it whole: across SSH, through the native bridge, into JSON.parse, for a
picture the thread shows as a path. The preview and window reads already
strip pictures on the host (#174); the tail could not, because the cursor
counts the file's bytes and a stripped line is shorter.

The tail now runs through one awk in the C locale, which prints each line's
size in the file, a tab, and the line with its pictures stripped. In the C
locale `length` counts bytes, so the cursor stays in the file's UTF-8 bytes.
The picture pattern is the one the other reads use, spelt out rather than
written with `{128}`: older mawk, Debian's awk for years, has no interval
expressions, which is why this was held. `fflush` sends each line as it
lands. A line without the frame counts as it reads.

The store's tests run the real script under sh: a megabyte screenshot line
crosses as under 10 KB, and the cursor after emoji, a picture and a data URI
matches the file. CI runs it with Ubuntu's mawk. The Demo frames its tail
the same way.

(cherry picked from commit 79e18b7)
herdr reuses workspace slots. When the host could not be reached, the
thread showed the chat's saved messages without checking whose they were
(#97), so a slot recycled since the cache was written showed its previous
conversation under the new chat's title until the host answered (HC-18).

Opened from the list, the thread knows which session the list saw, and the
offline seed now reads the cache only when it is that session's
(`seedIfSession`, as the instant reopen does). From a link or a notification
there is no session to check against, and it reads the cache as before.

(cherry picked from commit ff9a8d8)
…used

Both guards existed (HC-5): `saveHostKeyPin` stores nothing for an empty
fingerprint, and the transport refuses a connection whose host reported
none. Neither had a test, and an empty pin reads back as "no pin", which is
trust-on-first-use again on every connect. Now both do.

(cherry picked from commit db6d082)
An Antigravity chat read its history (07783b8), but its trust, permission
and question dialogs had to be answered in the terminal, the `/` palette
offered nothing, and its pushes said only the chat's state (HC-109).

The dialogs were read off a real agy 1.3.6 through `herdr pane read`, in a
scratch workspace, and answered there the way the phone will:

- A permission or a question is numbered, and the digit alone answers it:
  "1" ran the command, "2" chose Red, with no Enter. An Enter after it would
  land in the composer, so agy is `digitAnswers`.
- A long option wraps back to the first column under the menu. A row between
  two options, no further in than their numbers, is now the label above it
  going on, so "Yes, and always allow ... for commands that start with
  'echo'" shows whole. Claude's descriptions sit further in and are left.
- The permission names the tool above its rule and the command under
  "Requesting permission for:", which now become its details.
- The trust question has Claude's heading and answer but `>` for a cursor,
  and names the agent itself ("Antigravity CLI requires permission ..."). It
  is read with either cursor, named for the agent the screen names, with
  the folder joined where a narrow pane wrapped it.

The palette offers agy's own 35 commands from the registry in its binary;
those that end the conversation are filled, never sent from a tap. Its
`ask_question` calls fold as questions. The host watcher quotes agy's
replies and names the command it waits on (WATCHER_VERSION 8).

(cherry picked from commit c293282)
On iPad the list and the open chat are on screen together, each with its own
poll timer. Sharing an in-flight read (efca59d) only helped when the two
happened to overlap; on their own schedules the host was asked for every
snapshot twice. With no event stream, an idle chat and the list read 50
snapshots a minute where the chat alone reads 30.

HerdrClient now remembers the last snapshot it read, whoever asked, and
`recentSnapshot(maxAgeMs)` hands it out while it is younger than that. The
list's timer takes one younger than its own interval and schedules its next
round sooner by that age, so its rows are never older than the interval they
were before. Its first round, a round an event or a pull asks for, and every
other caller (a send's `currentPane`, the connection check) still ask the
host. Alone, as on a phone, the list's own last read is a full interval old
when its timer fires, so it asks exactly as before.

The budget test now mounts the list beside a chat on one Demo client: 30
snapshots a minute, the same as the chat alone (50 before), and 20 for the
list alone.

(cherry picked from commit 63833ea)
Two leftovers from the audit's dead-code task (HC-31):

- The `previews` table was created on every launch and deleted from on
  every reset and closed workspace, but nothing ever wrote a row: the list
  keeps its previews in memory and reads them on the first poll. It is
  dropped on launch now, as the old `unread` table was, and the deletes go.
- Diagnostics carried a `lastError` that was always null, on purpose: no one
  owns "the last thing that went wrong", and one screen's dismissed error in
  a bug report misleads. The slot and its line are gone rather than wired
  to whichever screen failed last.

(cherry picked from commit 72519d2)
…e ctx 0

The last three of the audit's small correctness debts (HC-23):

- Stop sent Escape and called it done when the host took the key. A host
  takes a key name it does not know as text, so that proved nothing. Stop
  now reads the agent's status for up to three seconds and says so when it
  is still working. It sends one Escape only: a second one into Claude opens
  its rewind menu. A status it cannot read counts as stopped, so a stop that
  may well have worked is never reported as failed.
- Settings showed one herdr version for the whole app, so after switching
  hosts it showed the previous host's until the new one's poll landed. The
  version is now kept per host, and Settings and diagnostics read the
  selected host's.
- A usage block of zeros showed "ctx 0" in the header between turns. Zero
  is now no label, like a missing count.

(cherry picked from commit efffb7a)
Both native modules decrypt a key with a passphrase, and both told a person
whose key failed to "check the passphrase", but the app always sent none
and had nowhere to type one (HC-7). A key with a passphrase, which is how
`ssh-keygen` makes one by default, could not be used at all.

- The host editor asks for the passphrase under a private key. It is kept
  in the keychain like the key, device-only, and cleared with it. A new key
  brings its own passphrase, blank for none; a kept key keeps its stored
  one unless another is typed.
- NativeChecks now makes an encrypted ed25519 and RSA key with ssh-keygen
  and proves the iOS code reads each with its passphrase and refuses it
  without one or with a wrong one.
- The guide and the module's README say which keys work where: ed25519 and
  RSA in OpenSSH format on both, ECDSA on Android only.

(cherry picked from commit 0913068)
…uest

Every socket request opened an SSH channel and started python3 on the host,
so a poll paid that twice a second per open chat and the list's fan-outs
queued behind each other on one channel (HC-95).

The python bridge now runs once per connection: requests go in on its stdin
tagged, answers come back on stdout with the same tag, and each request gets
its own socket connection on its own thread, so a waiting agent.prompt holds
up no read. Measured against herdr 0.9.5 here: three requests at once, answered
out of order, about 17 ms each.

The native modules gain writeStream (iOS: SwiftNIO's TTY stdin writer, checked
by NativeChecks against a private sshd; Android: the exec channel's output
stream). Where a transport cannot write to a command (the Demo) or the bridge cannot start, requests exec as before; three
bridges in a row that end without one answer stop the attempts for the
client. A timeout is the transport's, since nobody knows if the request
landed. The request size limit is kept for both paths.

(cherry picked from commit 5e06797)
…ice-locale times

A screen reader user heard nothing when the agent stopped to ask, the one
moment the app exists for (HC-32). The open chat now announces "<agent> needs
you: <question>" once when it blocks and "<agent> replied." when a new reply
with text lands; nothing while the history loads, which the screen reader
reads anyway.

tertiaryLabel is 3.78:1 in light mode, under the 4.5:1 text needs, yet words
were set in it: host key fingerprints a person is asked to compare, the
working timer, the swipe hint, command hints and section titles, a shell
run's time. They are secondaryLabel (6.67:1) now, and a test keeps tertiary
to glyphs.

Times and dates were en-US with a forced 24-hour clock, whatever the
device's region and 12/24-hour setting; they now use the device locale.

Of the audit's other HC-32 debts, the splash now matches both
backgrounds.

(cherry picked from commit 2310851)
woosal1337 and others added 9 commits October 26, 2026 15:39
On a simulator build without keychain entitlements, the host editor's
connection test passed and Save then did nothing at all: the keychain
refused the write (-34018), the press handler's promise rejected, and
nobody heard. A keychain that refuses a write on a phone, or a database
that cannot, would look the same. The editor now stays open and says what
failed under the buttons.

(cherry picked from commit ec026e9)
A Windows host showed in the chat list as
"sukuna: The command failed on the host (exit 1): 'export' is not
recognized as an internal or external command,". Windows OpenSSH runs cmd
or PowerShell unless told otherwise, neither parses the app's commands, and
cmd's words say nothing of the fix. Both shells' "not recognized" now read
as `windows_shell`, with the fix the getting-started guide gives: Git for
Windows bash as DefaultShell, DefaultShellCommandOption -c.

(cherry picked from commit 79a785a)
…ailing

On a real host, a new Claude chat in a folder Claude had not been trusted
with left New chat open over herdr's own words: "agent probe is blocked
during startup and is not ready for prompts". herdr answers agent_not_ready
there and keeps the agent registered, waiting at its question, so the
workspace and the agent were running while the sheet said it had failed.
That answer now counts as started: the chat opens, and its blocked bar asks
the question.

(cherry picked from commit d52094d)
On a real host, New chat with a mistyped folder made the workspace anyway:
herdr creates a workspace for a folder that does not exist in the home folder
instead, without a word, and the agent started in ~ with the permissions
just chosen for it. The folder is now checked first, on the host, and a
missing one is refused with dir_missing before anything is made. The check
also reads ~ as the home folder and hands herdr the absolute path.

(cherry picked from commit 99bebf8)
…macOS's sh exits 1

CI's Linux runner failed hostFolder.test.ts: its sh is dash, which exits 2
for a cd into a folder that is not there, where macOS's sh exits 1. The
client treats any non-zero exit as a missing folder, so the test now asks
for that.

(cherry picked from commit 22a707b)
Two tests broke only after the rebase onto main:
- #186's AgentsBar wrote its "N more" line and its figures in the
  tertiary label colour, which this branch's contrast test now refuses.
  They move to secondary; the bar's open/closed chevron is a glyph.
- #188's Test connection asks the socket bridge whether it is live, and
  the save test's mock client had no `socket.detect`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nGz6RQQsBfLJAGByzb1Bn
mawk, the default awk on Debian and Ubuntu, buffers what it reads from
a pipe; `fflush()` flushes only its output. Behind `tail -f` the live
tail printed nothing on those hosts, not even the first line. mawk's
`-Winteractive` reads a line at a time, but macOS awk refuses it, so the
script asks `awk -W version` on the host and passes the flag only to
mawk. The variable stays a single word, which zsh needs.

The tail test dropped `-f`, which is why it passed. A new test runs the
script as the host does and appends while it reads. Checked on Ubuntu
24.04 (mawk 1.3.4): 0 of 2 lines before, 2 of 2 after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nGz6RQQsBfLJAGByzb1Bn
Before the bridge, each request was an exec, and an exec past its
deadline dropped the native SSH client. That is how the app got off a
half-open connection. The bridge timed requests out on a JS timer only,
so on a dead link every poll, prompt and Stop waited out its own 15 s,
and nothing changed until TCP gave up minutes later.

- A request that times out ends its bridge: the channel is closed and
  what still waits is failed. The next call starts a new bridge, and its
  start deadline in the native layer drops the client if the link is gone.
- Waiting for a bridge to start now counts against the call's own
  deadline. The start itself carries on, so that native deadline still
  fires.
- After three bridges in a row end unanswered, the host execs for five
  minutes and then tries a bridge again. Before, it stayed on exec for
  the life of the client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nGz6RQQsBfLJAGByzb1Bn
This branch changes the watcher (Antigravity's dialogs) and moved it
from 7 to 8. Meanwhile main went to 8 with a different script. A host
that already runs main's 8 would keep it, so the stack's script needs
a number of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nGz6RQQsBfLJAGByzb1Bn
@cobanov

cobanov commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Rebased onto the new #176, with the two blockers from the review fixed:

  • mawk (bc0afd4). The tail asks awk -W version on the host and passes -Winteractive only to mawk; macOS awk refuses the flag. A new test runs the script with -f and appends while it reads. Checked on Ubuntu 24.04 with mawk 1.3.4: 0 of 2 lines before, 2 of 2 after.
  • Bridge timeout (86715e9). A request that times out now ends the bridge: the channel is closed and the other waiters are failed. The next call starts a new bridge, and the native start deadline drops a half-open client. Waiting for a start counts against the call's own deadline. After three silent bridges, the host now execs for five minutes instead of for good.

Also, from the rebase:

@cobanov
cobanov marked this pull request as ready for review October 26, 2026 13:22
@cobanov
cobanov changed the base branch from hc-107-visual-fixes to main October 26, 2026 13:26
@cobanov
cobanov merged commit 7dbab2c into main Oct 26, 2026
3 of 6 checks passed
@cobanov
cobanov deleted the remaining-gaps branch October 26, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants