Phase 6 — Panel frontend hardening (rides 0.19.0)#320
Merged
Conversation
…block + evict dead shared conn on manual disconnect Finding 1 (Critical, websocket.test.ts): the two long-tail-retry tests fired onopen between close cycles, which reset reconnectAttempts to 0 each cycle, so they passed under the pre-fix 3-attempt gate. Rewrote both to NEVER fire onopen between closes, so attempts accumulates: test 1 asserts state stays 'reconnecting' past attempt 3 (old gate would flip 'disconnected' terminal); test 2 asserts a new socket is constructed within 30000ms at attempt 7 where uncapped 5000*1.5^7 ~= 85s (old uncapped code would leave the timer unexpired). Verified both FAIL on a reverted old-shape connection.ts and PASS on the fixed code. Finding 2 (Important, connection.ts): the 'if (raw >= cap) this.reconnectAttempts = exp' block was a no-op (exp was just read from the same field) and the unconditional increment afterwards grew the counter regardless. Deleted the dead block; kept the Math.min cap on the delay. Replaced the misleading ponytail comment with an accurate one: delay is capped, counter grows unbounded but delay is bounded. Finding 3 (Important, use-websocket.ts): manual disconnect() tore down the shared conn for all subscribers but left the dead (manualClose=true, never reconnects) entry in _sharedSockets, so a later mount hit the reuse branch, attached a subscriber, replayed 'disconnected', and never called connect(). Added a urlRef and _sharedSockets.delete(url) in the manual disconnect callback so a later mount reopens a fresh conn.
|
Thanks for opening your first pull request on RoboCo! Quick checklist before review (most of these are enforced by CI, but worth a glance):
See CONTRIBUTING.md for the full workflow and the Code of Conduct for the community standards we follow. Welcome aboard — a maintainer will review shortly. |
…silent AGENT fallback)
…uard + result sidecar)
…ep so live cost reflects Anthropic cache spend
…ud 429 parks instead of crash-respawning
…ens roll into usage/cost summaries
…r's revisit counter survives a restart
…rates after 2026-08-31
…ilently falling back to a zero-usage env id
…ken-sweep test signatures)
…ck stays reachable
… tokens are bounded
…ry so a stolen cookie's exp is fixed
…t revokes the current jti
…w + jti revocation
[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation
[scan-fix] Phase 8: LLM providers / usage / billing
[Phase 7] schemas/conventions/MegaTask/API fixes (15 findings, scan-fix program)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 6 — Panel frontend hardening (rides 0.19.0)
Sixth phase of the 2026-07-06 full-codebase-scan fix program. Ten verified panel-frontend findings + an accept-as-is comment, all under the existing
## [0.19.0]CHANGELOG section. Stacks on Phase 5 (fix/scan-background-engines, PR #319) — base isfix/scan-background-engines; merge #319 first.Fixed
7f825736,377e65b5) — panel/ws/systemWebSocket is now shared across consumers (one ref-counted socket per URL in a module-level registry, fanning messages + state to all subscribers) instead of one socket peruseWebSocketmount.useA2ALiveStream+useRateLimitWebSocketnow share one/systemsocket. Long-tail reconnect: exponential backoff capped atWS_RECONNECT_MAX_INTERVAL=30swith no max-attempts freeze (the oldWS_MAX_RECONNECT_ATTEMPTS=3dead-cap that terminal-disconnected at attempt 4 is gone). A pong-timeout watchdog force-closes a dead socket atWS_PONG_TIMEOUT_MS=60sof no pong.377e65b5is the fix-wave: discriminating long-tail-retry tests (each FAILS on a gate-only revert AND a cap-only revert), the dead "freeze" if-block deleted, and manualdisconnect()evicts the shared conn.d1ae1ea4) —video-post-queuecaptions derive per render (editedX ?? post.x_caption ?? ""), mirroringx-post-queue.tsx:64-65. A CEO in-flight caption edit is no longer clobbered by a background refetch reinitializinguseState(post.x_caption ?? "").f2fbf15e) — settings page Save wired tosettingsApi.update(persist +invalidateQueries(["settings"])read-back); each control tracksedits ?? server ?? defaultper thetranscript-retention-card.tsxpattern. The Save button was previously decorative.1e2bc8f2) — tasks page passes single-selectstatus/team+limit: 500touseTasks(server-side filter via/tasks/summary); multi-select (>1) status/team +task_type/project/productstay client-side (backend doesn't accept them). Was fetching the whole backlog with{ q }only. DTO imported asTaskApiFiltersto avoid theTaskFiltersReact-component name collision.15fba310,2e839ebf) —useAgentsroster re-derives on live-status change: astatusEpochderived from theorchestratorStatus.agentssnapshot (${agent_id}:${state}joined) joins theuseAgentsqueryKey, so the existing 10suseOrchestratorStatuspoll invalidates the cached 5-min roster immediately. Keyed on the live snapshot, not the roster (circular → never changes). NorefetchIntervaladded.2e839ebfdrops theH18:spec-ref from the production comment.daf98181,a41223d7) —useMetricsreads agent counts from theuseAgentStatus10s poll cache (queryClient.getQueryData(dashboardKeys.agentStatus())+fetchQuerycold-cache fallback) instead of issuing its own redundant 60sgetAgentStatus()fetch. Writer + reader share the exact same key (verified no mismatch).a41223d7drops theM40:spec-ref + tightens the comment.2470b045) — scorecardrefetchInterval60s → 5min (extractedSCORECARD_REFETCH_INTERVAL = 300_000at module scope; bothuseMemberScorecard+useOrgScorecarduse it). Scorecard data changes slowly; 25 req/min → 5.d68a371f) — feature-flag off-transitions (true→false) confirm via RadixAlertDialogbefore the mutation fires; on-transitions stay immediate. Pending state tracks all in-flight toggles in aSet<string>(onMutateadd /onSettleddelete, immutable copies, error-path clear) — each row locks independently, replacing the old single-keytoggleMutation.variables?.keycheck that showed the wrong row as pending under rapid toggles.43a8f57c) — X/TikTok credentials clear-behind: emptying all four fields when credentials exist (isClearing = noneFilled && has_credentials) now confirms viaAlertDialogbefore the destructive clear. Normal save (any field filled) stays immediate. Both cards symmetric; the confirm handler uses confirm-time form values (react-query re-bindsmutationFneach render, no stale closure).46ecb310) — rate-limit storesyncFromApimerges by freshesthitAt(existing.hitAt >= entry.hitAt→ keep existing) instead of wholesale-replacing; an out-of-order older snapshot no longer regresses the displayed freshest hit. Omitted entries retained per the brief. A2A page invalidatesa2aLiveKeys.allon/ws/systemreconnect via auseEffectthat fires only on afalse → truetransition (prevConnectedref, initial-mount skipped, no storm).Accept-as-is
fd496fbe) — theproxy.tscookie-presence check is a UX redirect to/login, NOT a security boundary (the API/api/*enforces auth independently). Accept-as-is architecturally; a one-line clarifying comment added so a future reader doesn't mistake it for a control. No behavior change, no CHANGELOG bullet.CHANGELOG
fd496fbe) — Phase 6 entries appended under the existing### Fixedof## [0.19.0]. No new version section. CHANGELOG base blob verifiedbdc08a18before edit.End-of-branch fix wave
1f0acb61) — two stale-comment cleanups from the opus whole-branch review: deleted the misleading 3-line "pin attempts at the floor" comment inconnection.ts(the code never pinsreconnectAttempts, only caps the delay — the ponytail one-liner already says this), and tightened thetasks/page.tsx:163lead-in from "Fetch all tasks…" to "Fetch tasks (server-filtered for single-select) + client-side multi-select extras". Comment-only, no behavior/test change.Verification
pnpm lint+pnpm typecheck— clean.pnpm vitest run— 47 files / 304 tests pass.edited ?? serverValuepattern consistent with existing references); TDD spot-checks (H15, M40, C4 long-tail) each fail on pre-fix for the right reason; binding constraints verified across all 15 commits (signed, no co-author, no@ts-ignore/eslint-disableadded, no spec refs in production code, AGPL-3.0 only, no new dependencies — Radix AlertDialog was already in the project). One Minor dismissed (cosmetic prettier reflow ofbuildPairtest signature — prettier-compliant, reverting fights the formatter).Model note
Implementers ran on sonnet; per-task reviews on sonnet; whole-branch review on opus. kimi-k2.7-code:cloud is not routable in this harness (flagged to CEO; accepted).
🤖 Generated with Claude Code