Files
AetherForge/PROBLEMS.md
AetherForge 415b5dc6a3
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Release validation: tests green, USB pack, fleet UX and API hardening.
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
2026-06-06 16:57:39 -07:00

29 KiB
Raw Blame History

Open — medium / by design

| Issue | Notes |

|-------|-------|

| bof_execute disabled | Agent returns explicit error — in-memory BOF execution disabled for safety (client.go). |

| Process hollowing AMSI/ETW | Relocation patching done; Defender/ETW detection causes ~50% failure — bypass not implemented (hollow_windows.go). |

| Cloudflared on Linux server | In-process tunnel start is Windows-only; Linux/macOS builds use no-op stub — use external connector (AF_TUNNEL_EXTERNAL) or add cross-platform launcher. |

| macOS camera / GPU miner | Stubs return "not supported"; Linux has V4L2 + nvidia-smi path. |

| Agent WireGuard auto-download | Windows ensureWGExe() downloads WireGuard on first Path Tracer use — heavy side effect; pre-install recommended. |

| Non-Windows Path Tracer agent parity | pathtracer_stub.go returns error on wg_setup; Linux/macOS agents cannot join WireGuard chains. |

| KEV heuristics | Non-Windows agents return catalog with Status: n/a — Windows-only CVE matching.

Open — large / deferred

| Issue | Notes |

|-------|-------|

| Non-Windows agent parity | pathtracer_stub.go returns error JSON on wg_setup; Linux/macOS cannot join chains. UI filters platform; server does not validate platform field. |

| In-memory sessions only | TraceSession map in handler RAM — server restart loses session state while agents may still hold tunnels; no DB persistence or startup sweep. |

| WireGuard auto-download | Windows ensureWGExe() silently downloads/installs WireGuard on first use — heavy, needs admin, no progress UI. |

| No PathTracerPage Vitest | Page logic (polling, chain selection, QR modal) untested; only uiHelp.test.ts covers help keys. |

| No agent-side pathtracer tests | pathtracer_windows.go / stub have zero Go tests. |

| NAT / symmetric UDP | UPnP + DB IP fallback may still yield unreachable endpoints; no STUN/TURN or connectivity probe after configure. |

| Fixed listen port 51820 | All hops use same UDP port — multiple agents behind one NAT may conflict; no per-hop port allocation. |

| Agent display name vs hostname | WS auth UpsertAgent overwrites name with hostname on every connect — operator-renamed fleet labels may not appear in Path Tracer unless re-saved after connect. |

Open — medium / UX

| Issue | Notes |

|-------|-------|

| Visual island (green vs deck) | Page uses #00ffaa WireGuard chrome while data-operator-deck='pathtracer' sets blue accent (operatorDeck.css); intentional branding split (see DV-07). |

| Status polling only | UI polls /status every 2s — no WebSocket push for hop progress; acceptable latency but stale during orchestration. |

| Error path leaves tracing UI | On status.error, tracing clears but session remains server-side until operator clicks End or TTL — hops show failed state in chain panel. |


Open — large / deferred

| Issue | Notes |

|-------|-------|

| No CI-automated HTTP forge | scripts/e2e-validate.ps1 -ForgeAgent still manual (multipart fusion, long compile). Live compile gated behind LIVE_FORGE=1 + -tags liveforge. |

| PathForge test gaps | Only TestPathForgePlacedExcludesHintFile; no skipped-counter, lock-original, or frontend UI tests. |

| Forge cancellation / batch races | Cancel token API tested in isolation; concurrent batch forge + mid-compile cancel not covered. |

| Non-Windows forge host limits | PE disguise (disguise_stub.go) and Windows signing (sign_stub.go / osslsigncode) are platform-constrained by design. |

| Mac PathForge runtime dependency | .command launchers curl /api/download/agent-mac at runtime — requires reachable server_url and pre-placed agent binary. |

Open — low

| Issue | Notes |

|-------|-------|

| pathforge_test.go dead loop | Lines 66–72 iterate entry.Files but assert nothing — harmless test noise. |

Open (document-only / deferred)

| Issue | Notes |

|-------|--------|

| Dual storage without sync policy | Complex cross-tab sync — session preferred over local; aetherforge-auth event on logout |

| Flaky progress simulation vs. real compile time | Cosmetic — stage timeline caps at 94% until server responds (45 min client timeout) |

| Path Forge / batch fusion test gaps | Cancellation, partial batch failure, cancel-token races — needs dedicated tests |

| DashboardPage tests emit ECONNREFUSED stderr | Failure-path tests; happy-dom hits localhost:3000; tests pass |

| DownloadButton mock aliasing pattern | Document for new download helpers — shared mock fn already in components.test.tsx |

--

Open (2026-06-06 audit — LARGE)

  • tunnel_stream not implemented — server-side TCP reverse relay documented as future work (README.md).

  • Server cloudflared launcher no-op on non-Windows — Linux server deploy cannot auto-launch tunnels (cloudflared/launcher_stub.go).

Low (open)

  • mergeConfig partial-PUT UseTLS legacy behavior — omitted keys may not reset bools; see config_test.go.

High (open)

  • Non-Windows Path Tracer agent parity — pathtracer_stub.go returns error on wg_setup; Linux/macOS agents cannot join WireGuard chains.

Medium (open)

  • Agent WireGuard auto-download: Windows agent ensureWGExe() downloads and silently installs WireGuard from download.wireguard.com on first Path Tracer use if not already present (agent/client/pathtracer_windows.go). Heavy side effect; no server-side fix — operator should pre-install WireGuard on fleet hosts or accept first-run download latency.

  • Mac PathForge .command still depends on server_url + /api/download/agent-mac at runtime (now validated at forge time).

| Op | Command | Notes |

|----|---------|-------|

| SMB share enumeration | smb_shares | Windows + Remote Aggressive; ARP/subnet hosts ? net view JSON |

| Spread status | spread_status | In-memory last sweep (deploy/spread_status.go); read-only |

| Credential names | credential_vault_list | Win Credential Manager / macOS Keychain / Linux secret-tool + ~/.ssh paths — names only |

| Secure wipe | secure_wipe | Overwrite-then-delete folder; system-root guards; confirm in UI |

| Port-forward matrix | tunnel_ssh_forward × N | CruciblePortForwardMatrix — multi-row grid on selected Windows nodes |

UI: Phase C controls in CrucibleExpandedOps.tsx Fleet Maintenance (replaces “coming soon” stubs).


Linux / macOS parity

| Area | Status |

|------|--------|

| Mining + hashrate | RandomX pure-Go engine works on Linux/macOS; stats loop sends hashrate_15s/1m/15m + shares over WS. |

| Idle schedule guard | Fixed — SystemCPUPercent was always 0 on Unix (reporter_unix.go), blocking idle-mode mining; Linux uses /proc/stat, macOS uses sysctl kern.cp_time. |

| Screenshot | Linux: scrot / ImageMagick import / gnome-screenshot. macOS: screencapture. |

| Camera | Linux V4L2 via ffmpeg/fswebcam (camera_linux.go). macOS: stub. |

| File ops | Cross-platform (file_ops_unix.go / file_ops_windows.go). |

| Posture | Unix collectors return firewall/AV/patch data (posture_unix.go), not all n/a. |

| Spread | SSH path on Linux/macOS (autospread_unix.go); SMB/WinRM Windows-only by design. |

| Firewall ops | Linux ufw/iptables (firewall_linux_ops.go); macOS pf + socketfilterfw (firewall_darwin_ops.go); other Unix stubs. |

| GPU miner | Windows-only T-Rex path; Linux/macOS stub with detect-only. |

| Docker E2E | docker/docker-compose.yml — isolated agent + server; see docker/README.md. |

Open

  • Client: WebSocket/beacon paths integration-only in CI (Docker Tier 2 closes Linux slice).

  • macOS: camera, GPU miner — stubs or partial; firewall aggressive ops implemented (see Backend BE-04).

  • Linux screenshot: headless containers need xvfb + scrot or custom command field.

  • bof_execute permanently disabled — handler always fails (client.go); product/safety decision.

  • Linux/macOS GPU RVN mining broken — detectGPU() may find NVIDIA, but spec() downloads Windows .exe miners (gpu_miner.go, gpu_detect_stub.go).

  • Mesh P2P without -tags p2p — default build reports 0 peers; UI exposes mesh_status with re-forge hint (mesh_p2p_stub.go).

  • Process hollowing AMSI/ETW bypass not implemented — documented ~50% failure rate (hollow_windows.go).

  • KEV exposure scan non-Windows — all CVEs marked n/a (cve_scan_stub.go).

  • Unknown Unix CPU stats stub returns 0 — can break idle-mining guard on exotic platforms (cpu_stub.go).

Open (document-only / deferred)

| Issue | Notes |

|-------|--------|

| Dual storage without sync policy | Complex cross-tab sync — session preferred over local; aetherforge-auth event on logout |

| Flaky progress simulation vs. real compile time | Cosmetic — stage timeline caps at 94% until server responds (45 min client timeout) |

| Path Forge / batch fusion test gaps | Cancellation, partial batch failure, cancel-token races — needs dedicated tests |

| DashboardPage tests emit ECONNREFUSED stderr | Failure-path tests; happy-dom hits localhost:3000; tests pass |

| DownloadButton mock aliasing pattern | Document for new download helpers — shared mock fn already in components.test.tsx |

| server/webroot not auto-synced on npm run build | Manual step via devrun.bat or copy; stale webroot served old hashed assets |

| Vitest stderr noise | FleetTopologyMap three.js tags warn in happy-dom — tests pass |


Open (needs product copy or broader pass)

| ID | Issue | Notes |

|----|-------|-------|

| UH-01 | Crucible expanded ops (spread/tunnels/recon buttons) | 40+ buttons still rely on title= only — no HelpTip on each CrucibleExpandedOps action |

| UH-02 | Fleet Roster / Agents page bulk toolbar | Filter chips and bulk actions lack inline help |

| UH-03 | Emberwake / War Room campaign widgets | Funnel stages need HelpTip parity with Command Deck funnel |

| UH-04 | Mission Deck | Page has minimal operator guidance |

| UH-05 | Builder mission wizard chips | Inline blurbs exist on chips; not all advanced forge sections have HelpTip (see docAnchors.test gap list) |

| UH-06 | Settings tabs beyond Calibrate/Forge | Alerts, webhooks, desktop push sections partially covered |

Open (visual / UX debt)

| ID | Issue | Files / notes |

|----|-------|----------------|

| DV-01 | Neon cyan fragmentation — canonical token is --neon-cyan: #00e8f5 but components hard-code #00f5ff, #00e5ff, and #0ff fallbacks | AgentRemoteActions.css, HashrateChart.tsx, BuildManagerPage.tsx, ProtocolTunnelPanel.css, FileManager.css (fixed), sacred geometry SVGs |

| DV-02 | Page header patterns diverge — most pages use deck-hero + eyebrow; Build Manager uses bm-header / bm-title; Path Tracer uses green .pt-title (#00ffaa); Forge says “The Forge” vs nav “Forge” | BuildManagerPage.tsx/css, PathTracerPage.css, BuilderPage.tsx, Layout.tsx NAV |

| DV-03 | SacredPageHeader unused — component + .page-header--sacred CSS exist but no page imports it; dead design path | SacredPageHeader.tsx, sacred-geometry.css |

| DV-04 | Pages.css duplicate / conflicting rules — two .empty-state blocks (L192 vs L1078); second .page-header h1 block overrides visual-polish gradient when page CSS loads after global polish | Pages.css; load order via per-page imports |

| DV-05 | Default dashboard subtitle — out-of-box copy is “security is just an emotion” until Calibrate overrides dashboard_subtitle; reads as placeholder to new operators | DashboardPage.tsx L70; SettingsPage.tsx L131 |

| DV-06 | Path Tracer buried in nav — desktop sidebar lists it last; mobile hides it under “More” while Crucible/Forge are primary tabs | Layout.tsx MOBILE_PRIMARY vs MOBILE_MORE |

| DV-07 | Path Tracer visual island — green WireGuard aesthetic (#00ffaa) does not use operator-deck page classes or brass/neon deck chrome | PathTracerPage.css, PathTracerPage.tsx (missing operator-deck-page) |

| DV-08 | Chart lazy-load placeholder is invisible — ChartPlaceholder is an empty div at 35% opacity; advanced charts pop in with layout shift | DashboardPage.tsx L51–53 |

| DV-09 | Dead chart badge styles — .chart-live.sample / .blend in wealth-deck CSS; resolveChartSeries no longer emits sample mode | wealth-deck.css; chartSampleData.ts |

| DV-10 | Docs vs in-app naming drift — wiki says “Calibrate (Settings)”, “Command Deck”, “Forge / Builder”; nav uses “Calibrate”, “Command Deck”, “Forge”; Emberwake route was /spread redirect | public/docs/index.html, Layout.tsx, App.tsx |

| DV-11 | Public spread landing vs Emberwake — /spread/ static kit uses aether.css deck tokens (good) but typography/spacing differs slightly from in-app Emberwake cards | public/spread/assets/aether.css, EmberwakePage.css |

| DV-12 | Emoji in status bar / actions — ?? DOCS pill, agent action buttons (?? ? ?) inconsistent with otherwise SVG-icon nav | SystemStatusBar.tsx, AgentRemoteActions.tsx |

| DV-13 | No light theme — entire product is dark-only; docs wiki matches but no prefers-color-scheme path | global styles |

| DV-14 | Sidebar version hard-coded — footer shows v0.0.1 regardless of server build | Layout.tsx L321 |

| DV-15 | Mission Deck formatting — source file has excessive blank lines (likely formatter artifact); harder to maintain, no runtime impact | MissionDeckPage.tsx |

Remaining doc gaps (need product copy / user input)

| Topic | Notes |

|-------|-------|

| Default dashboard subtitle | “security is just an emotion” until Calibrate override — marketing copy decision (DV-05) |

| SocGholish / fake-update lander | Documented in spread playbook #third-party tab; no shipped branded HTML template |

| OAuth redirect abuse playbook | Research only — no Entra app wizard in Emberwake |

| Earnings USD quote | TEST_RESULTS.md notes low priority; not wired |

| Screenshot placeholders in wiki | [Screenshot: …] divs — need real captures from operator deck |

| Sidebar version v0.0.1 | Hard-coded in Layout.tsx — should read server build version (DV-14) |

| Light theme | Dark-only documented; no prefers-color-scheme path (DV-13) |


Deferred — needs refactor, heavy mocks, or external deps

| Area | Why deferred |

|------|--------------|

| agent/cmd/mine-validate | Standalone CLI (main package); exercises RandomX + live Stratum; run manually or in mining CI |

| Cloudflared Start with real token | Spawns/downloads binary + network; only empty-token path covered |

| FleetScheduler.Start loop | 1-minute ticker; logic covered via direct tickInterval/tickCron in same-package tests |

| CruciblePage full integration | Large page; helpers + CrucibleExpandedOps + FileManager tested separately |

| Fleet panel widgets | Partially covered via components.test.tsx; full panel flows need WS mocks |

| Path Forge / batch fusion cancel races | Needs dedicated cancel-token harness |

| Playwright E2E (server/web/e2e/) | Phase 8 of test-suite; requires build + temp server |

| Docker mining E2E | .github/workflows/ci-docker-mining.yml — separate tier |

| Mesh P2P | Requires -tags p2p build tag |

| Platform-specific fusion launchers | OS-gated; crypto path covered in media_crypto_test.go |


| Suite | Result | Notes |

|-------|--------|-------|

| go test ./... (repo root) | N/A | No root go.mod; use per-module dirs (documented in Integration audit). |

| agent/ | PASS | All packages ok (client ~3s). |

| server/ | PASS | internal/api ~69s, internal/builder ~32s. |

| fusion/ | PASS | Includes media_crypto_test.go (untracked in git at time of pass). |

| server/web Vitest | PASS | 66 files / 587 tests (3 consecutive full runs). |

| Item | Notes |

|------|-------|

| Vitest stderr ECONNREFUSED 127.0.0.1:3000 | Failure-path / bulk-command tests in DashboardPage, AgentsPage, etc. (see Dashboard section). |

| FleetTopologyMap three.js ref warnings | happy-dom; cosmetic stderr. |

| Vite build chunk size warnings | three / vendor bundles > 500 kB; not a test failure. |


Bugs — Builder/Security (Bug Team 4 audit, 2026-06-06)

Scope: server/internal/builder/, fusion/, server/config.go, cross-cutting API input validation and auth edge cases.

Fixed in this pass

| ID | Fix |

|----|-----|

| BLD-01 | PowerShell injection in uninstaller — processName and persistenceKey were embedded in single-quoted PS1 strings without escaping apostrophes. A WorkerName like foo'; Invoke-Expression …; ' would break out of the string. Now uses strings.ReplaceAll(…, "'", "''") for both fields, consistent with how installRel was already escaped (uninstall.go). |

| BLD-02 | JSON build request body unbounded — non-multipart POST /builder/build decoded r.Body without a size limit; a 1 GiB JSON body would buffer entirely. Fixed: http.MaxBytesReader capped to 512 KiB before json.Decode (handler.go). |

| BLD-03 | Unbounded backup pool/URL arrays — BackupServerURLs, BackupPools, and RVNBackupPools from user input are concatenated verbatim into the generated Go source (generateBuiltinConfig). A request with 10 000 entries would produce a multi-MB .go file, slowing or crashing go build. Fixed: arrays truncated to 10 entries each in normalizeRequest (handler.go). |

| BLD-04 | Partial build dir not cleaned on failure — when compileWorker, buildFusionFromRequest, writeUninstallScript, or MkdirAll fail mid-build, the entire builds/<uuid>/ tree (containing a copy of agent source + uploaded fusion payload) was left on disk. Added cleanupBuild() closure that calls os.RemoveAll(buildDir) on each failure return path (handler.go). |

| BLD-05 | Weak random password entropy — generateRandomPassword() used only 4 random bytes (8 hex chars, 32-bit entropy), guessable in ~4 billion attempts. Increased to 8 bytes (16 hex chars, 64-bit entropy) (router.go). Test updated. |

| BLD-06 | Malformed config.json silently ignored — json.Unmarshal failure was swallowed; operator saw no indication their config was rejected and defaults were running instead. Added fmt.Fprintf(os.Stderr, …) warning on parse failure (config.go). |

Deferred / large

| ID | Severity | Location | Description |

|----|----------|----------|-------------|

| BLD-D1 | High | server/internal/builder/pathforge.go | No server-side restriction on root_path. PathForgeRequest.RootPath is passed directly to filepath.WalkDir without any whitelist or prefix check. An authenticated operator (or compromised session) can supply any absolute path (e.g. C:\Windows\System32, /etc/) and the server will (a) enumerate every matching file and (b) write .exe, .bat, and .command companion files next to them. Fix: validate RootPath against an operator-configured allow-list (e.g. specific USB/NAS mount points), or at minimum reject absolute paths that escape a configured data_dir. |

| BLD-D2 | Medium | server/internal/builder/pathforge.go batContent / macContent | Filename injection in generated scripts. d.Name() (raw filesystem filename) is interpolated into .bat and .command scripts via fmt.Sprintf. On Windows, a filename containing %VAR% expands the BAT variable; a filename with " breaks the quoted argument. On macOS/Linux, a filename with ' breaks single-quoted shell strings, and a server_url containing ' would allow shell command injection in the generated curl line. Fix: escape " and % for BAT content; escape ' ? '\'' for shell content. |

| BLD-D3 | Low | server/internal/builder/handler.go buildUniversalAgent | No partial build cleanup for universal builds. The cleanupBuild fix (BLD-04) covers single-platform buildAgent. The corresponding buildUniversalAgent / finishSpreadKit / finishUniversalFusion do not clean up buildDir on internal failures. Extend the same pattern. |


Bugs — Server (Bug Team 1 audit, 2026-06-06)

Deferred / large

| ID | Severity | Location | Description |

|----|----------|----------|-------------|

| SRV-B1 | High | server/main.go | No graceful shutdown on SIGINT/SIGTERM. Fixed. http.ListenAndServe replaced with http.Server + goroutine; signal.NotifyContext(syscall.SIGINT, syscall.SIGTERM) drives a srv.Shutdown(ctx) with 10 s timeout on signal, allowing all defer calls (cloudflared.Stop(), database.Close(), maintenance.StopRetentionJobs(), fleetSched.Stop()) to run cleanly. |

| SRV-B2 | Medium | server/internal/db/sqlite.go | SQLite max-connections not configured. Fixed. db.SetMaxOpenConns(1) added immediately after sql.Open; the single-connection pool eliminates concurrent-writer WAL-lock contention and SQLITE_BUSY errors under load. |

| SRV-B3 | Medium | server/internal/scheduler/fleet_scheduler.go | O(tasks × agents) DB queries per minute. Fixed. BulkLastFleetTaskRuns added to the db package; tickInterval now pre-fetches all relevant fleet_task_runs rows in a single query and checks an in-memory map[string]time.Time (keyed "agentID:taskID") in the nested loop — 1 query per tick instead of tasks × agents. |

| SRV-B4 | Medium | server/internal/api/websocket.go broadcastDashboard | Stale-conn cleanup races with HandleDashboardWS teardown. Fixed. broadcastDashboard no longer spawns a goroutine to delete the dashboard map entry on write failure. It only closes the connection; HandleDashboardWS already owns all map cleanup via its existing defer, so the double-delete and the spurious presence_update{online:false} are eliminated. |

| SRV-B5 | Low | server/internal/api/agent_ws_limiter.go | Rate-limiter map never purges zero-entry keys. Fixed. delete(agentWSRateLim.attempts, clientIP) called when len(filtered) == 0 after the expiry sweep; map keys are reclaimed as IP addresses churn out of the window. |


Backend

  • db.New / MkdirAll: Already returns error on failure (server/internal/db/sqlite.go); remove stale “Low (open)” note in Server API section when editing that doc block.

Frontend

  • RemoteDirBrowser removal: No broken imports. UI logic lives in FileManager.tsx + src/help/remoteDirBrowser.ts; remoteDirBrowser.test.ts covers helpers. Deleted RemoteDirBrowser.tsx / .css are not referenced elsewhere.

  • FileManager.test.tsx: Untracked but picked up by Vitest (src/**/*.test.{ts,tsx}); 10 tests pass — add to git when committing Fleet work.

  • Download test mocks: Prefer separate vi.fn() per export when mocking api/download (real module aliases downloadApiFile to downloadAuthedFile; shared mock caused order-dependent flakes).


Open — large / architecture

| Issue | Notes |

|-------|-------|

| Monolithic WebSocket context | Every useWebSocket() consumer re-renders on any WS state change. Split into FleetContext / EventsContext or selector hook (useAgents()) for true isolation. |

| CruciblePage size (~2k lines) | Single component owns terminal, fleet list, tabs, file manager — hard to memoize subtrees; consider section components + React.memo boundaries. |

| Per-agent stats_update broadcast | Backend sends one dashboard message per agent stats tick; no batching/coalescing in websocket.go. Fleet of N agents ? N JSON parses/frame on client. |

| SystemStatusBar REST poll duplicates WS | Polls listAgents every 15s though fleet already streams via WebSocket — wire readout to WS or drop agent poll. |

| Vite chunk size | three (~600 kB) and vendor bundles trigger build warnings; FleetTopologyMap loads three on Dashboard — already lazy but still heavy first open. |

| No terminal virtualization | DOM cap at 400 lines helps; full virtual list (react-window) needed for 2000-line scrollback without mount cost. |

| MatrixRain always mounted in Layout | Runs on every route including mobile (hidden sidebar but component still mounts on desktop). Consider content-visibility or route-gated mount. |

| Path Tracer 2s REST poll | No WS push for hop progress; acceptable but adds load during orchestration. |

Open — medium

| Issue | Notes |

|-------|-------|

| Emberwake war room double feed | 15s client poll + 30s server runWarRoomBroadcast — redundant; prefer WS-only with poll fallback. |

| CursorFire + SacredGeometry on all routes | Desktop-only effects still mount with Layout; gate on VisualEffectsContext or route. |

| Earnings estimate on every hashrate change | Dashboard debounces via totalHashrate effect — could share chart sampler interval. |


Deferred — large / complex

| Issue | Location | Notes |

|-------|----------|-------|

| WebSocketProvider async race | context/WebSocketProvider.tsx | Fixed (2026-06-06). Added openingRef (useRef(false)) in-flight guard -- a second connect() call while a ticket fetch is in progress returns early. Added AbortController (ticketAbortRef) to cancel any prior in-flight fetch; aborted invocations bail before creating a WebSocket. openingRef is always reset in a finally block. |

| useVisibleInterval calls fn on every dep change | hooks/usePageVisible.ts | Fixed (2026-06-06). fn is now stored in a fnRef (useRef). The effect depends only on ms, enabled, and visible -- an unstable fn reference no longer re-runs the effect or triggers an extra immediate call. The interval always invokes fnRef.current() so callers always see the latest fn without extra renders. |


Deferred — large / medium

| ID | Issue | Location | Notes |

|----|-------|----------|-------|

| BA-03 | write() has no write deadline | client/client.gowrite() | Fixed. c.conn.SetWriteDeadline(time.Now().Add(15*time.Second)) is now called immediately before WriteJSON (and cleared afterward), so a stalled TCP socket cannot hold c.mu indefinitely and deadlock share submission, stats, and command-result goroutines. |

| BA-04 | SSH/SCP spread commands have no overall timeout | deploy/autospread_unix.go — attemptSSHSpread() | ConnectTimeout=3 limits only the TCP handshake; after a successful connection, scp.Run() and ssh … start.Run() have no deadline. A slow or unresponsive host stalls the goroutine indefinitely, holding a slot in spreadSem (16 total). With 16 such hangs in flight, all future spread goroutines block waiting on the semaphore. Fix: use exec.CommandContext with a ~30 s deadline wrapping the whole SCP + SSH sequence. Fixed: exec.CommandContext with context.WithTimeout(30s) wraps the full SCP + SSH sequence; a hung host releases its semaphore slot after 30 s. |

| BA-05 | GPU miner binary download has no HTTP timeout or body-size cap | client/gpu_miner.go — downloadAndExtract() | http.Get(url) with no timeout and io.ReadAll(resp.Body) with no size limit. A slow redirect or a response that trickles bytes forever will hang the goroutine; a gigabyte-scale response could OOM the agent. Fix: use an http.Client with a 5-min overall timeout, and wrap the body in io.LimitReader(resp.Body, 512<<20). Fixed: http.Client{Timeout: 5*time.Minute} + io.LimitReader(resp.Body, 512<<20). |

| BA-06 | CollectFullSysCheck blocks for 45+ s on empty subnets | client/syscheck.go — CollectFullSysCheck() | Calls deploy.ScanLocalSubnet(56) synchronously in the command handler goroutine. ScanLocalSubnet probes up to 56 hosts × ~0.8 s timeout = ~45 s on an empty /24. During this time the agent cannot process incoming WebSocket messages (it is running in a command goroutine, but the read loop is separate; however if the server has a short reply timeout the session may drop). Consider running the subnet scan in a background goroutine and returning a "pending" token, or reducing the default maxHosts for syscheck. |


Infrastructure

  • Root fix.py, fix3.py, fix4.py: Untracked one-off Python string/regex editors targeting CruciblePage.tsx (ProtocolTunnel onDispatch, GPU hashrate label). Intended changes appear already applied in CruciblePage.tsx. Safe to delete after review; not run in CI — do not rely on them for builds.

  • CI: Only .github/workflows/ci-docker-mining.yml (Docker mining); no root Makefile test target found.

  • data/login-credentials.json, data/users.json: Untracked; do not commit (secrets/local data).