Skip to content

Fleet frugality: SSH ControlPersist 10m (PHNX-2582) + shared Linear request budget (PHNX-2310) - #3294

Merged
muqsitnawaz merged 4 commits into
mainfrom
fleet-frugality-phnx-2582-2310
Aug 30, 2026
Merged

Fleet frugality: SSH ControlPersist 10m (PHNX-2582) + shared Linear request budget (PHNX-2310)#3294
muqsitnawaz merged 4 commits into
mainfrom
fleet-frugality-phnx-2582-2310

Conversation

@muqsitnawaz

Copy link
Copy Markdown
Contributor

Two disjoint fleet-frugality changes, one worktree, one commit each. Both reduce
repeated cost when the fleet fans out — SSH handshakes and Linear API requests.

PHNX-2582 — SSH ControlPersist raised to 10 minutes (c4bfb81)

The OpenSSH multiplex master only survives while it stays idle under
ControlPersist. The dominant repeating fleet touch is the daemon's 5-minute
service loop (CATCHUP_TICK_MS and the reap/sync services, all 5 * 60_000),
which is longer than the old 60s window — so every poll's master had already
died and each poll paid a fresh TCP+auth handshake (~100-300ms direct, up to
~500ms relayed; measured on the live fleet 2026-08-10). At 60s, 100% of a
5-minute poll's cost was connection setup.

  • Extract the value as SSH_CONTROL_PERSIST_SECONDS (10 min), which clears the
    5-minute cadence with margin so a periodic --device/fan-out poll lands warm
    (~3-5ms).
  • Bounded on purpose: a master reused after a host sleeps costs a ~45s
    ServerAlive teardown, and a wider window only widens that chance; fan-outs stay
    bounded regardless by their own per-peer timeoutMs.
  • Test asserts the emitted option and that the value exceeds the 5-minute
    poll; the three call-site tests that pinned the 60s literal now track the
    constant.

The ticket's other cheap win — "pre-filter fan-out by last-known-reachable so a
dead box cannot bound a parallel sweep" — is already implemented:
isDialableDevice() (cli/src/lib/devices/registry.ts) gates every fleet
fan-out (remote-agents-json.ts, session/remote/*, feed/watch.ts,
channels/owner-forward.ts), and REMOTE_STDOUT_MAX_BYTES caps each peer's
capture. The agents serve --control channel the ticket named was removed under
RUSH-3001, and the ticket owner's decision was explicitly to tune
ControlPersist, not swap the transport
— which is exactly this change.

PHNX-2310 — Shared per-key Linear request budget (de85a31)

~13 concurrent drain agents on one Linear API key, each a separate short-lived
process, had no shared view of request spend — so a drain could exhaust Linear's
2500 requests/hour limit and throttle ticket-status reads fleet-wide.
linear-cache.ts already caches reads and backs off after a 429; that is
reactive — by then the budget is gone.

  • New cli/src/lib/linear-rate-limit.ts: a proactive, cross-process hourly
    budget keyed by the API key. fetchLinearIssuesPage reserves against it before
    each request; when the pool is spent it serves the last cached (stale) snapshot
    instead of forcing a 429 — the same graceful degradation the card already uses
    on any other failure.
  • Lock-free on-disk state, mirroring usage-backoff.ts: one empty stamp file per
    request over a sliding one-hour window; count = readdir after sweeping
    elapsed stamps. Two concurrent reservers create different files and neither can
    clobber the other, so N processes coordinate with no shared mutable document to
    race on.
  • Budget (2400) sits under the hard 2500 to leave headroom for a human on the key
    and absorb the small check-then-create overshoot. State is keyed by a hash
    of the key, so the raw credential never lands on disk and two keys spend
    independently.
  • Tests exercise the real filesystem path: budget exhaustion + refill as stamps
    age out, per-key isolation, cross-invocation coordination, and that the raw key
    is never written into a path.

Verification

Full vitest suite on fleet workers via cli/scripts/test.sh --shard 6 — output
quoted in a comment below. Affected-area runs locally:
ssh-exec.test.ts + the two updated call-site suites (77 passed) and
linear-rate-limit.test.ts + linear-project-counts.test.ts +
linear-cache.test.ts + usage-backoff.test.ts (63 passed). tsc clean.

Muqsit and others added 2 commits August 29, 2026 23:13
…ections (PHNX-2582)

The OpenSSH multiplex master only survives while it stays idle under
ControlPersist, and the dominant repeating fleet touch — the daemon's
5-minute service loop — was longer than the old 60s window, so every poll
paid a cold TCP+auth handshake (~100-300ms direct, up to ~500ms relayed;
measured on the live fleet 2026-08-10). With 60s, 100% of a 5-minute poll's
cost was connection setup.

Extract the value as SSH_CONTROL_PERSIST_SECONDS (10 minutes) so a periodic
--device / fan-out poll lands on a still-warm master (~3-5ms) instead. The
window stays bounded — a master reused after a host sleeps costs a ~45s
ServerAlive teardown, and a wider window only widens that chance; fan-outs
stay bounded regardless by their own per-peer timeoutMs.

The reachability pre-filter and fan-out buffer cap the ticket also names are
already in place: isDialableDevice() gates every fleet fan-out on
last-known-reachable, and REMOTE_STDOUT_MAX_BYTES caps each peer's capture.

Test asserts the emitted option and that the value exceeds the 5-minute poll;
the three call-site tests that pinned the literal now track the constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…500/hr (PHNX-2310)

~13 concurrent drain agents on one Linear API key, each a separate short-lived
process, had no shared view of request spend — so a fleet drain could exhaust
Linear's 2500/hr request limit and throttle ticket-status reads everywhere.
linear-cache.ts already caches reads and backs off AFTER a 429, but that is
reactive: by then the budget is gone.

Add the proactive half: linear-rate-limit.ts, a cross-process hourly budget
keyed by the API key. fetchLinearIssuesPage reserves against it before each
request; when the pool is spent the accumulator serves the last cached
(stale) snapshot instead of forcing a 429 — the same graceful degradation the
card already uses on any other failure.

The budget is lock-free on-disk state, mirroring usage-backoff.ts: one empty
stamp file per request over a sliding one-hour window, count = readdir after
sweeping elapsed stamps. Two concurrent reservers create different files and
neither can clobber the other, so N separate processes coordinate with no
shared mutable document to race on. The budget (2400) sits under the hard 2500
to leave headroom for a human on the key and absorb the small check-then-create
overshoot. State is keyed by a hash of the key, so the raw credential never
lands on disk and two keys spend independently.

Tests exercise the real filesystem path: budget exhaustion + refill as stamps
age out, per-key isolation, cross-invocation coordination, and that the raw
key is never written to a path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@muqsitnawaz

Copy link
Copy Markdown
Contributor Author

Full-suite verification (cli/scripts/test.sh --shard 6)

Ran the whole vitest suite across 6 fleet workers. Both diff-affected areas are green; the only two failures are pre-existing under-load flakes in files this diff does not touch, and both pass cleanly in isolation (72/72):

  • self-update.test.ts:1152 (ensureGlobalBinLinks foreign-path repair) — this is the already-filed flake PHNX-3477 ("flakes under load").
  • pty-client.test.ts:131 (auto-detects a real bun build --compile executable) — env-dependent execPath detection under the sharded worker; bin resolved to .local/bin/node instead of the fixture.
$ bun x vitest run src/lib/self-update.test.ts src/lib/pty-client.test.ts
 Test Files  2 passed (2)
      Tests  72 passed (72)

Diff-area runs, all green:

ssh-exec.test.ts + cloud/factory.test.ts + browser/drivers/ssh.test.ts   77 passed
linear-rate-limit.test.ts + linear-project-counts.test.ts +
  linear-cache.test.ts + usage-backoff.test.ts                            63 passed

tsc clean. The required GitHub test check (single-runner, --retry=2) is the authoritative gate and is not subject to the sharded-under-load flake.

* uses and keeps the path short.
*/
function keyDir(apiKey: string): string {
const hash = crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
@muqsitnawaz

Copy link
Copy Markdown
Contributor Author

Non-author review (code-reviewer subagent — prix-cloud is paused, #1767)

Verdict: CHANGES REQUESTED (3 non-blocking quality/doc-accuracy findings; the underlying mechanisms verified correct). Addressing all three in a follow-up commit:

  1. SHOULD — ControlPersist rationale cites 5-min daemon services that never SSH (ssh-exec.ts, docs, changelog, test comment). The fixed 5-min daemon ticks (catchup/reap) don't touch SSH; the periodic SSH services (usage-sync/auth-sync) tick at 15 min. Fix: reground the rationale in the real dominant caller (repeated ad-hoc --device/fan-out touches arriving over minutes), drop the nonexistent-service claim.
  2. SHOULD — "fleet-wide" overstates the budget's scope (changelog, docs/projects.md). State lives under getCacheDir() = per-machine, unsynced; two machines on one key each budget 2400 independently. Fix: reframe to per-machine + call out the cross-device aggregate as a known limitation (the module docstring already says "this box").
  3. SHOULD — no test for the fail-open write-failure branch (linear-rate-limit.ts catch → true). Fix: add a test that forces the write to throw and asserts reserveLinearRequest still returns true.

Verified correct (not flagged): the reserve catch-returns-true fail-open matches usage-backoff.ts's sanctioned pattern; sliding-window budget races are bounded by the 2400<2500 headroom; createdMsOf rejects malformed names; per-key isolation via SHA-256; the PHNX-2582 isDialableDevice/REMOTE_STDOUT_MAX_BYTES cheap-win claim is true; no other file hardcodes the old 60s.

Muqsit and others added 2 commits August 29, 2026 23:25
…ail-open test (PHNX-2582, PHNX-2310)

Three non-blocking findings from the code-reviewer subagent:

1. ControlPersist rationale cited the daemon's 5-minute service loop, but those
   ticks (catchup/reap) don't touch SSH, and the periodic SSH services
   (usage-sync/auth-sync) tick at 15m. Reground the docblock, ssh-transport.md,
   changelog, and test on the real dominant caller: repeated ad-hoc
   --device / fan-out touches of one box, arriving in bursts over minutes. The
   value (10m) is unchanged.

2. "fleet-wide" overstated the Linear budget's scope. State is under
   getCacheDir() = machine-local and unsynced, so it budgets the agents on ONE
   box; two boxes on one key each budget independently. Reframe changelog,
   docs/projects.md, and the module docblock to per-machine and name the
   cross-device gap as a known limitation.

3. Add a test for the fail-open write-failure branch: point the state dir under
   a file (deterministic ENOTDIR), assert reserveLinearRequest still returns
   true and linearRequestsInWindow reads 0.

No behavior change — docblocks, docs, changelog wording, and one added test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The controlOpts() docblock in ssh-exec.ts and the trade-offs section of
ssh-transport.md still carried the retracted 'dominant 5-minute fleet poll'
narrative after the first follow-up corrected the primary spots. Reground both
on the multi-minute --device/fan-out burst framing (and point the function
docblock at SSH_CONTROL_PERSIST_SECONDS so the rationale lives in one place).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@muqsitnawaz

Copy link
Copy Markdown
Contributor Author

Non-author review — final verdict: APPROVE (clear to merge)

The code-reviewer subagent re-verified commit 867e0ee0f against the diff and confirmed all three CHANGES-REQUESTED findings are resolved:

  1. ControlPersist rationale — regrounded on the real dominant caller (repeated ad-hoc --device/fan-out bursts over minutes); the two residual stale copies in controlOpts() and the ssh-transport.md trade-offs section are fixed, and the rationale is now consolidated behind {@link SSH_CONTROL_PERSIST_SECONDS}. Only accurate 15-min usage-sync/auth-sync references remain.
  2. Linear budget scope — changelog/docs/module docblock corrected to per-machine, with the cross-device aggregate named as a known limitation.
  3. Fail-open branch — deterministic ENOTDIR test added (8/8 pass).

Verified independently: tsc clean; linear-rate-limit.test.ts 8/8. The two sshExec timeout-detection failures the reviewer observed are a pre-existing, diff-unrelated sandbox flake (they reproduce on unmodified origin/main and pass 25/25 on the CI-style runner) — not a regression.

Merging on green.

@muqsitnawaz
muqsitnawaz merged commit 4ee28e4 into main Aug 30, 2026
8 of 9 checks passed
@muqsitnawaz
muqsitnawaz deleted the fleet-frugality-phnx-2582-2310 branch August 30, 2026 04:04
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