Skip to content

v0.9.2: remote latency, PR panel, watcher loop, delivery and update fixes - #92

Merged
mubeda merged 36 commits into
mainfrom
mubeda/pr-module-latency
Oct 14, 2026
Merged

mubeda merged 36 commits into
mainfrom
mubeda/pr-module-latency

Conversation

@mubeda

@mubeda mubeda commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

What Changed

The v0.9.2 release candidate: work for #88, #89, #90, #93, #94 and #95 from the 2026-10-13 profiling session, PR #91 (preview gateway over BiBCode Connect, closes #68) merged in, and the version bump with the curated changelog.

Issue Change
#89 remote clients Thread subscribers get patch items (changed fields; per array the new length and changed entries) instead of the whole thread per event; thread updates coalesce 100 ms, shell snapshots 500 ms. Server messages of 16 KiB or more are raw-deflate compressed before E2EE encryption when the client offers deflate-v1 (approved with safeguards; BIBCODE_REMOTE_COMPRESSION=0 turns it off). The Profiler emits rpc.bytes per method every 5 minutes.
#90 PR panel glab runs without its telemetry request; Approve's permission reads run in one wave and failures log their code; the panel paints from stored copies for GitHub and GitLab, then reads live.
#88 watcher loop Rescans after inotify overflows are spaced (30 s doubling to 30 min). On Linux the worktree watch skips directories Git ignores and nested repositories or linked worktrees (git ls-files --ignored --directory confirmed by git check-ignore); new directories are added by a worker (path-scoped check-ignore), .gitignore writes re-read the rules, submodules keep notify's recursive watch. One checkout: ~312,000 → ~2,400 watched directories.
#93 Delivery uncertain A Claude result before the new message's replay no longer ends the new turn, so the thread stays running and the next message is queued; a replay after the 60 s wait marks the delivery delivered.
#94 update relaunch Under a systemd service unit the update relaunch starts the new AppImage through systemd-run --user --scope and exits only once it has left the unit; the Profiler raises crash.native for coredumps of BiBCode processes no session covered (cursor advances only after a successful coredumpctl query).
#95 Windows upgrade lanes desktop-upgrade-smoke.yml takes previous_tag (release runbook: run from v0.7.2); Windows lanes require the updated app to relaunch with a window.
Skill catalog server.getProviderCapabilities answers from a cache (per instance, directory and settings generation; 60 s fresh, background refresh; partial catalogs uncached; 64 entries).
#91 Preview gateway over BiBCode Connect, Connect tunnel restored at startup, link proofs over HTTP/1.1, tunnel connector covered by the Profiler.
Also Hide discovered worktrees after catalog refreshes; joining VCS summary subscribers get the branch at once; Create PR/MR dialog shows only Done; flate2 recorded in the dependency ledger and privacy contract.

Validation

Not in this PR

  • Webview memory growth (profiler 6b0bedc9a176, ~50 MB/h on v0.9.1): needs a heap snapshot first.
  • Ignored directories inside submodules stay watched (as before).

Profiler coverage

Compression runs on a blocking thread inside the writer's timing. New events: rpc.bytes; new finding source: unrecorded coredumps (crash.native). Watcher Git calls run through the supervised ProcessRunner. The relaunch helper is the app itself (logged on failure). New logs: late-delivery confirmation, ignored early Claude result, skipped running publication, background capability refresh failures, systemd-run fallback.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1

mubeda and others added 14 commits October 12, 2026 15:50
Relay-connected clients now open server-loopback dev servers through the
preview gateway at their own https origin, and the Connect tunnel comes
back after a server restart. Refs #68. Also proposes the SOCKS-routed
preview profiles of #69 (design only, with the Linux WebKitGTK spike).

- relay: provision 4 preview hostnames per managed endpoint
  (<env>-p1..p4: proxied CNAMEs plus ingress rules before the catch-all),
  found by name so re-linking adds missing ones and deprovisioning removes
  them before the tunnel; the runtime config carries hostname and
  previewHostnames.
- server: the tunnel runtime publishes its hostnames; a gatewayOpen caller
  on loopback under the endpoint hostname takes a free preview slot (a live
  target never loses its slot) and gets origin https://<preview hostname>;
  new reasons relay-not-provisioned and relay-busy. An outermost main-port
  layer serves preview hostnames through the slot's target handler,
  HTTPS-facing (Secure cookie, https Origin rule and Location), never
  BiBCode's routes, CORS, or admission gate. Preview responses are
  Cloudflare-CDN-Cache-Control and Cache-Control no-store, service worker
  scripts are refused, and relay hostnames over plain HTTP redirect to
  HTTPS. The gateway drain starts when shutdown is signaled and is shared
  by every caller.
- server: restore the Connect tunnel from the stored link at startup,
  unless the server now listens on another port than the tunnel was
  provisioned for; link, unlink, and restore share one lock;
  connect.linkState reports managedEndpoint, previewHostnames,
  managedEndpointStale, and linkGeneration, and relayConfig refuses a
  background re-link whose requireLinkGeneration no longer matches.
- web: relay resolution (and any server-returned origin) bootstraps on the
  returned origin and maps it back to localhost per tab; browser mode never
  frames these previews; the primary client, once authenticated, re-links
  once per session, silently and fenced, when its tunnel lacks preview
  hostnames or points at an old port.
- docs: remote architecture, user guide, relay README, a relay step in the
  cross-platform runbook, the #68 spec (with review addenda) and plan, and
  the #69 proposal.

Native relay validation (desktop and browser-mode client over a deployed
relay stage) is recorded in the runbook and was not run here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the request was created or found, the dialog kept a Close button next
to Done. It now shows only Done, and the form's lock comes from the request
state itself, so a status re-read after creation cannot unlock the fields or
bring back the create action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
The connector now also starts at boot (tunnel restore), so its own death
must reach the Profiler like any other child BiBCode runs (AGENTS.md:
Profiler, Crash, and Trace Coverage).

- A watcher checks the active connector every 5 s without reaping it. An
  exit nobody asked for withdraws the relay preview hostnames, logs a
  warning, reports subprocess.exit and subprocess.crash (Watch::Failures,
  labelled by the connector's executable), and ends the rest of its
  process group. It holds the runtime weakly, so a runtime dropped without
  shutdown still drops its connector.
- apply and shutdown report a connector that died on its own since the
  last check before replacing or stopping it; the Windows liveness probe
  no longer consumes the process handle.
- Relay hostname matching on every main-port request no longer clones or
  allocates.
- Docs: Profiler section, profiler skill events and findings, remote
  architecture, and a connector-crash step in the relay runbook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ms (#90)

Approved design. Every GitLab read costs a 2.3 s glab start, half of it a
synchronous telemetry request, and the panel waits on getContext before
any stored copy can paint. The design serves the last context and stored
copies first, refreshes them in the background, extends stored copies to
GitHub, turns glab telemetry off, and runs Approve's pre-check reads in one
wave.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
glab 1.122 sends a telemetry event to the GitLab host before each command
returns. On a VPN link that is a second HTTPS connection per call: 2.3 s
per `glab api` read instead of 1.2 s. One helper, provider_cli_environment,
now sets GLAB_SEND_TELEMETRY=0 for the pull request runner, merge request
lookup and creation, and Settings discovery. The user's own glab
configuration is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
connect.linkProof compared the proof's loopback origin with the request
URI's authority, but an HTTP/1.1 request carries an origin-form target
with no authority, so every BiBCode Connect link and background re-link
from a real client failed with "Invalid managed endpoint origin." The
check now falls back to the Host header, as the auth layer does; tests
had passed absolute URIs and never hit the real shape.

Also pins the preview-hostname label boundary (a 60-character label
keeps its hash whole, a 61-character one shortens its stage prefix),
tests that gatewayOpen and relayConfig payloads decode with and
without the new optional fields, and corrects the contracts comment
on `origin`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Approve and every other GitLab action re-read the merge request,
approvals, reviewers, GraphQL metadata and project one after another
before acting: five glab starts in a row, about 6 s with telemetry off and
11.5 s before. They are independent, so they now run together, as the
detail read already does.

A failed action now logs its error code with tracing::warn!, never its
message, which can carry checkout paths and host names. The one recorded
Approve failure left no code behind.

The action test fixture answers concurrent reads by role; roles now queue,
so a later group's read of the same argv takes the later response.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
A preview hostname is public, and every request to it refreshed its
target's idle clock before any session check, so anyone polling the
hostname (or a stale tab's reload client) kept the target, and its
relay slot, from ever closing; four such targets left every open
failing relay-busy. Now only a bootstrap or a request carrying a
gateway session counts as activity. That request holds the target open
through its principal check, so the idle sweep cannot close the target
under it before its lease takes over.

Addresses the Codex review on #91.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel showed a skeleton until getContext answered, and that read runs
the CLI's authentication probe plus three host reads: 0.65-3.3 s on a VPN
link once the 30 s server cache expired. Each live available context is now
stored with the other pull request display copies. The panel's first read
sends `allowStale`; the server answers from the stored row (`stale: true`)
with no host call, and the panel reads live at once and replaces it. Older
clients never send the flag and keep reading live.

- Stored rows are fenced by read order: unusable answers leave a tombstone,
  and a logout or a Rescan that rejects the host raises a per-host fence on
  the database worker, so an earlier read never writes back.
- A self-hosted host is recorded again from its stored context after a
  restart, so neither getContext nor readSnapshot needs CLI discovery.
- The stored lookup and the live fallback share getContext's 30 s deadline
  and one scope resolution.
- Each stored answer gets one live read; a remounted panel joins the read in
  flight, and a failed live read keeps the stored context on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Stored display copies were GitLab-only, so every GitHub list and pull
request waited on `gh` (about 2 s for the list). Successful live reads now
store them for both hosts, `readSnapshot` serves both, and a successful
action deletes that number's rows for both. Only GitLab routes subscribe
and poll; GitHub stays on demand.

A GitHub list ignores stored copies once its live first page answers: with
no poller to correct it, a late stored page judged newer by a skewed server
clock would otherwise stay on screen.

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

On the Linux desktop the Git watcher's inotify thread ran at 99% CPU for
hours while the process grew to 3.3 GB. An overflow asks for a full
re-registration of the worktree's watches, and on Linux that walk queues an
open event per watched directory. A tree larger than the kernel's
16,384-event queue (235,371 directories here, mostly node_modules of nested
agent worktrees) therefore overflowed again on every re-registration: about
80 million watches were added in a loop.

A per-watch gate now spaces full re-registrations: the first runs at once,
later overflows defer the next one 30 s, doubling to 30 minutes, reset
after a quiet stretch; a single timer retries the deferred one. Status keeps
its sticky fallback reads meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
A remote client over Tailscale waited 7-10 s per terminal keystroke while
the server answered in 0 ms: every event on an open agent thread re-read and
re-sent the whole thread (40,170 activities, 6-10 MB) and every event
anywhere re-sent the whole shell. The link carried 2.2 GB in a few hours and
small replies queued behind it.

- A thread subscriber that sends `patches: true` now gets `patch` items
  against the snapshot it holds: changed fields, removed fields, and per
  array its new length plus only the entries that differ, by index. The
  client decodes just those values with the thread's field schemas
  (`applyThreadPatch`). Older clients keep whole snapshots; older servers
  ignore the flag.
- Thread updates coalesce 100 ms after the first event of a burst, the
  shell 500 ms, so streamed text no longer reloads the thread per delta.
- `fixtures/thread-stream/lifecycle.json`, written by a server test, pins
  that the patches rebuild the server's snapshot exactly. Sending raw events
  instead was rejected: the client reducer diverges from the server
  projection (session stop, turn timing, activity sequence, message order).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
"Keep hidden" sends the catalog generation the client saw, and the server
demanded an exact match with its current authoritative snapshot. Every
completed refresh advances the generation, even when nothing changed (window
focus, the 2 s poller, Git Manager revalidation), so a remote client that
sees each generation seconds late failed every time: "Catalog generation 9
is no longer authoritative; current generation is 10". The service now
records the oldest generation since which the adoptable worktree paths are
unchanged and accepts any generation in that range, hiding exactly what the
client was shown; a set that changed since is still refused, now with a
tracing warning.

Also: a subscriber joining a VCS summary producer that had already published
waited up to 30 s for its first value, because the watch receiver treated the
current value as seen. It now gets the current summary at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Oct 13, 2026
mubeda and others added 3 commits October 13, 2026 16:12
…pression design

The new summary test committed without a configured identity, which works on
a developer machine but exits 128 on CI runners. It now sets user.name and
user.email like its neighbours.

Adds the approved design for compressing large server messages before E2EE
encryption (#89), with its length side-channel safeguards.

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

Over a slow remote link, first thread loads and reconnects still send
multi-megabyte JSON snapshots that hold every other reply for seconds.
Ciphertext does not compress, so the WebSocket's permessage-deflate cannot
help an E2EE connection; compression has to happen before encryption.

A client that can inflate raw deflate offers `deflate-v1` in `e2ee_auth`.
When the server confirms it, a server message of at least 16 KiB leaves as
a 4-byte big-endian uncompressed length plus raw deflate (level 3, on a
blocking thread), every record flagged 0x10, whenever that is smaller.
Client messages and control records stay uncompressed. The 64 MiB limit
applies to the uncompressed size on both sides; the client refuses a larger
declared length or output that differs from it.

The client keeps arrival order while a message inflates: later messages
wait until its handler's effect has started, effects still run
concurrently so a backpressured stream holds up no other reply, and a
message dropped before its handler ran (bad data, or a close first) stops
delivery so nothing overtakes it. A closed socket cancels its inflation,
and the server's compression job shares the message bytes and stops
between chunks once its write is dropped.

The design's length side-channel trade-off was approved with safeguards:
server-to-client only, per message, 16 KiB threshold, negotiated, and
`BIBCODE_REMOTE_COMPRESSION=0` stops the server confirming it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…r ends its turn (#93)

After an idle-suspended Claude thread resumed, Claude sometimes reported one
or two `result` frames before it replayed the message BiBCode had just
written (3 of the last 19 deliveries on the affected thread; one more was
earlier work finishing). The runtime had already started the new turn, so
that result completed it. Acceptance then arrived with the terminal
revision moved, `publish_running_session` skipped `running`, and the
thread read `ready` while Claude worked for minutes. The next message was
therefore written as a start instead of queued; Claude, busy in tool calls,
did not replay it within 60 s, and it showed "Delivery uncertain".

A turn now begins when Claude replays its input. Between a normal
delivery's write and the replay of that same write (matched by its
acknowledgement registration, so another write's replay does not count), a
successful result belongs to work Claude was still finishing and is ignored
(logged). An error result still ends the turn, since Claude can fail before
reading, and once the write's acknowledgement wait ends without its replay,
results end turns as before. The skipped running publication is logged too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
mubeda and others added 8 commits October 13, 2026 17:51
…red (#93)

When Claude reads a message only after its 60 s acknowledgement wait, the
delivery stays "Delivery uncertain" although Claude got it, and Retry would
send it a second time. The expired write already keeps its place in the
acknowledgement order; its registration now carries the outbox delivery key,
and the replay it consumes emits an internal `delivery.confirmed-late` event.
The event pump moves that thread's matching uncertain row to delivered; a
row retried or dismissed meanwhile is left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
The replay can arrive between the acknowledgement timeout and its
`sending -> uncertain` write. The confirmation now runs off the event pump
and waits up to a second for a matching row that is still sending, instead
of dropping the only confirmation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
`server.getProviderCapabilities` started the provider CLI on every call;
for Claude that is 1.2 to 1.5 s on the server (profiler findings
6f4b4ab71016 and 9e6264fb967c, 170+ slow calls in a day), paid each time the
composer opened and again on remote clients.

Successful catalogs are now cached per instance and directory for the
current provider-settings generation. Entries younger than 60 s are answered
as is; older ones are answered at once while a single background discovery
refreshes them. A settings change drops every entry, and failures are never
cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
To see which methods fill a slow remote link, the session now counts each
method's replies and stream items where the connection already encodes them
before queueing (E2EE and record-framed sockets), so no reply is serialized
twice. Every 5 minutes the latency detector emits one `rpc.bytes` event per
method with message count, total and largest encoded size. Counting runs
only while the profiler is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
A catalog that reports discovery issues (for example Claude failing to
reload skills) asks the user to retry, so it is no longer cached. The
cache keeps at most 64 catalogs, evicting the oldest, so removed worktrees
do not stay for the server's lifetime, and a failed background refresh is
logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…t and report missed crashes (#94)

On the v0.9.0 -> v0.9.1 AppImage update the relaunched app and both crash
monitors died with SIGBUS seconds after the restart, and the Profiler raised
nothing. The desktop ran as the main process of a `systemd-run --user`
service (KillMode=control-group, ExitType=main): when the old process exited,
systemd stopped the unit and killed its cgroup, the relaunched AppImage's
FUSE mount included.

- Desktop: after an installed update, a host that sits directly in a
  `.service` unit relaunches the new AppImage through
  `systemd-run --user --scope` at the last exit callback, so the new app
  lives in its own scope. If systemd-run fails, Tauri's relaunch runs as
  before.
- Profiler: every start now asks systemd-coredump for crashes of this same
  executable since the last session began (whatever that session's state)
  and raises a `crash.native` finding for each one no session reported,
  grouped by signal and linked by `coredump-<pid>-<time>` so it is raised
  once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
A response guard picks the reply it sends after admission, so only an upper
bound is known where bytes were counted; counting it skewed totals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
#95)

Windows users on v0.7.2 reported that "update and restart" closed BiBCode
without the new version starting. The seeded-upgrade lanes could not catch
it: they always seeded from the latest stable release, and the Windows
cleanup passed when nothing was running.

- `desktop-upgrade-smoke.yml` takes an optional `previous_tag` input
  (validated as a stable `vX.Y.Z`, passed through the environment) that
  seeds every packaged lane, and the release runbook runs it from v0.7.2.
- After the candidate is installed, the Windows lanes wait for the
  installed executable to be running with a window (`tasklist /V`), so a
  missing relaunch fails the lane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
mubeda and others added 11 commits October 13, 2026 18:37
…watch (#88)

notify's recursive inotify watch adds a watch for every directory below the
worktree, so a checkout with nested agent worktrees and build output held
~235k watches; re-registering them overflowed the kernel queue, and each
overflow asked for another full re-registration (99% CPU, 2.5 GB).

On Linux the native backend now walks the worktree itself and watches each
directory non-recursively, skipping the directories Git ignores (one
`git ls-files --others --ignored --directory` per registration, confirmed
by `git check-ignore` so a directory whose current contents merely happen
to be ignored stays watched) and nested repositories or worktrees, which
have their own watches. A worker thread watches directories created later
the same way, skipping ignored directory names. The affected main checkout
goes from about 312,000 watched directories to about 2,400. macOS and
Windows keep their single native recursive handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…sh queries

- The update relaunch exits only after the new process has left the
  service's cgroup (it is in its systemd scope); if systemd-run exits or
  does not move within 5 s, it is stopped and Tauri's relaunch runs.
- The unrecorded-crash query advances a `crash-query-cursor` only after
  coredumpctl answered, so a start where it failed or timed out retries the
  same window next time instead of skipping it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…ch check

A busy runner can make `tasklist /V` hit its bound; that sample now counts
as not yet relaunched and polling continues within the restart budget,
while a listing that cannot run at all still fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
The E2EE compression change added `flate2` (raw deflate, zlib-rs backend
already in the graph through `zip`). It performs local compression only and
adds no network or telemetry path; the zero-telemetry contract's server
dependency list and the dependency-upgrade ledger now account for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Review follow-ups to the pruned worktree watch:

- Directories created later are checked with `git check-ignore` by path,
  one call per new level, so an anchored rule such as `/build/` no longer
  skips `src/build/`; names are used only when Git cannot answer.
- A removed and recreated directory is watched again.
- A `.gitignore` or `.git/info/exclude` change re-reads the rules and
  watches what they now keep.
- Submodules (a `.git` file pointing into `.git/modules/`) stay watched,
  since the parent's status shows their changes; linked worktrees and other
  repositories are still left to their own watches.
- A watch the worker cannot add reaches the watcher callback as an error.
- Git runs through the supervised process runner.

The unrecorded-crash query now tells coredumpctl's "No coredumps found"
from a failed listing, so the cursor never advances past records it did
not read. Also fixes a clippy lint in the watcher.

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

- Only writes to `.gitignore` or `.git/info/exclude` re-read the ignore
  rules. Opening the file also produced an event, and Git opens it to
  answer, so one read started an endless loop of Git runs and walks.
- Submodules (their Git directory in this worktree's `modules/`, resolved
  rather than matched by substring) are walked with their own ignore rules.
- When a later `check-ignore` fails, new directories named `node_modules`
  or `target` are skipped instead of treating the failure as "nothing
  ignored".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…0.9.2 work

PR #91 lets the preview gateway be reached over BiBCode Connect, accepts
Connect link proofs requested over HTTP/1.1, counts only session traffic as
relay target activity, and keeps the tunnel connector covered by the
Profiler. Its own CI passed (28 checks). Merged here so the v0.9.2
candidate carries both PRs and is verified together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Pruning inside submodules needed each submodule's own ignore rules,
exclude files, Git queries and fallback, and broke when a submodule was
initialized after the watch began. A submodule (resolved from the
superproject's `modules/` each time, so a late one is recognized) now
keeps notify's recursive watch, as before the pruned watch; the
superproject around it stays pruned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
…k failed ones

- A directory created inside a recursively watched submodule is no longer
  sent through the pruned additions, which re-registered it non-recursively
  and dropped notify's recursion beneath it.
- A recursive watch that fails partway is unwatched before the error is
  reported, so no unrecorded watches stay behind.
- The profiler maintenance test no longer asserts that a stale events file
  survives until maintenance: in a full test run another test's event can
  open an events file whose retention removes it first. Findings
  compaction, which only maintenance does, still shows the hourly run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
The filter that leaves a submodule's contents to its recursive watch also
matched the submodule root, so a submodule directory removed and created
again never got its recursive watch back. Only strict descendants are left
to it now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
Bump the application manifests and Cargo.lock to 0.9.2 and add the curated
v0.9.2 changelog entry covering #88, #89, #90, #93, #94, #95, the PR #91
preview gateway over BiBCode Connect (#68), the skill catalog cache, and the
Create PR/MR dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RsdQGFUPxjNZ8GQocThxT1
@mubeda mubeda changed the title Fix PR panel latency, remote stream flooding and the Git watcher loop v0.9.2: remote latency, PR panel, watcher loop, delivery and update fixes Oct 13, 2026
@mubeda
mubeda merged commit b623035 into main Oct 14, 2026
35 of 37 checks passed
@mubeda
mubeda deleted the mubeda/pr-module-latency branch October 19, 2026 04:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Internal browser: preview gateway reach for BiBCode Connect (relay) environments

1 participant