Every ssh.exec opened its own connection: a TCP handshake, a key exchange and an
authentication round trip per command. A key rotation paid for that eight times,
a deployment six, and refreshing M profile states M times.
Connections are now kept per server. The three risks that made this worth doing
carefully are handled explicitly:
- Staleness. A pooled connection can be dead exactly when it matters. Liveness is
tracked through error, close and end, and a lease that finds a dead entry opens
a new one. The remaining race, where the connection dies between the check and
the command, is caught by the retry rule below.
- Retrying. Only a failure that proves the command never reached the server is
retried, and only once, and only on a connection that was already established
before this call. execClient marks exactly that case, when the channel fails to
open. A command that opened a stream is never repeated, because the server may
already be acting on it - repeating a deployment is not this layer's decision.
Two tests hold that line: widening the rule to any failure fails both.
- Lifetime. Idle connections close after a minute, the pool is reference counted
so a shared connection survives until its last user is done, closeAll runs
during quit, and every pooled client keeps a standing error listener so an
error while idle cannot reach the uncaughtException handler.
A trust-on-first-use connection is never pooled: it was established without
verifying the fingerprint, so it must not serve a later verified call. A change
to host, port, user, auth type, key path or trusted fingerprint invalidates the
pooled connection.
ssh-service coverage rises from 61% to 90% of lines and 97% of functions.
Also in this commit, the smaller items from the same review:
- Diagnostics batched records that queue up while a write is in flight into one
append, and chmod runs once per file instead of once per record. At the debug
level every IPC call writes a line, which is exactly when troubleshooting.
- The set that suppresses duplicate deployment notifications is trimmed instead
of growing for the lifetime of the process.
- The updater kept the same once('error') pattern on its spawned helper that
took the app down through the SSH client.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1.3 KiB
1.3 KiB
ForgeFlow architecture audit
Generated 2026-08-23T13:06:16.630Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
Files above 750 lines
| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |
|---|---|---|---|---|---|
| No findings. |
Files above 1,000 lines
| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |
|---|---|---|---|---|---|
| No findings. |
Cyclomatic hotspots
| File | Lines | Decisions | Functions | IPC handlers | Responsibilities |
|---|---|---|---|---|---|
src/renderer/actions/shell.js |
518 | 101 | 86 | 0 | inventory, deployment, git, renderer, updates |
src/renderer/app.js |
732 | 80 | 124 | 0 | inventory, deployment, git, renderer, security, updates |
src/main/server-inventory.cjs |
578 | 89 | 104 | 0 | inventory, deployment, git, security, updates |
src/main/git-service.cjs |
687 | 111 | 118 | 0 | git, security |
src/main/unraid-inventory-methods.cjs |
708 | 76 | 93 | 0 | inventory, deployment, git, security, updates |
Interpretation
Files above 750 lines require decomposition. Files above 1,000 lines are release blockers unless a concrete technical exception is documented. Mixed responsibility and IPC hotspot lists are available in the JSON report.