Skip to content

fix: 2026-08-20 audit remediation — socket race, tmux targeting, hook fail-open, revocation, drag visuals - #312

Merged
arzafran merged 5 commits into
mainfrom
fix/audit-2026-08-20-remediation
Aug 20, 2026
Merged

fix: 2026-08-20 audit remediation — socket race, tmux targeting, hook fail-open, revocation, drag visuals#312
arzafran merged 5 commits into
mainfrom
fix/audit-2026-08-20-remediation

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

Remediates the actionable findings from today's correctness audit in one PR: closes a crash-capable threading race in the socket server, makes tmux-compat scripts fail loudly instead of acting on the wrong workspace, stops a quit Programa from blocking agents' next prompts, makes revoking a paired phone actually cut its live session, and unfreezes sidebar drag visuals.

Summary (finding IDs from docs/audits/codebase-audit-2026-08-20.md)

  • H1/M7/M9 — every off-main tabManager access routed through v2MainSync (7 reads + 1 misplaced write); notification.clear now synchronous; 8 MiB cap on the per-connection line buffer (payload_too_large + close).
  • H3/M2/L1/L2 — session-qualified tmux targets resolve by full title, then session-name-for-window-0/1, else error; session-start/prompt-submit hooks fail open on transport errors like teardown already did (all three agents); focus/close-window propagate real errors; layout apply sends the caller's cwd as documented.
  • H5 — live mobile-bridge connections registered per endpointId and closed on revoke.
  • H2/M3TabItemView.== compares the two drag bindings its body reads; declined autosaves no longer poison the fingerprint and retry boundedly (5 × 1s cap).
  • M10/M11 — remote-daemon spec downgraded to DAEMON-ONLY for the unwired resize coordinator; ⌘⇧T documented.
  • M6 was already fixed on main (4166020) — verified, no change.

Deliberately excluded (need decisions, not code): M4 (prompting on insecure-HTTP restore is a product call), M5 (reparent unification needs visual verification), M6's navigation-stale refs + M8 handle-map pruning (API/design changes), M12 (remote-host GC).

Review order: commit 1 (socket threading) is the load-bearing one; 2–5 are independent.

Test Plan

  • Unit + socket-integration CI green (threading changes covered by the socket integration suite)
  • Manual: drag a sidebar tab — dragged row dims and the drop indicator tracks
  • Manual: quit Programa mid-Claude-session, submit a prompt — hook exits 0, agent continues
  • Manual: Settings → remove a paired device during an active session — phone disconnects immediately

No regression tests were added for the threading fixes: races and hook transport failures have no unit seam (socket-integration CI exercises the handlers), and per test policy no shape-assert tests were written.

…er; sync notification.clear

Audit 2026-08-20 H1/M7/M9. @mainactor on TerminalController is advisory under
Swift 5 mode, so every off-main tabManager access must go through v2MainSync —
seven read sites and one write placement (v2WindowCreate's setActiveTabManager
ran on the connection thread after its lookup closure returned) did not.
notification.clear replied ok before its async mutation ran, so clear-then-list
raced. The per-connection line buffer had no size cap; one newline-less write
could grow it without bound — now closes with payload_too_large at 8 MiB.
…rrors; honest CLI errors

Audit 2026-08-20 H3/M2/L1/L2. A session-qualified tmux target (sess:2) silently
discarded the session name and resolved against the flat workspace pool — now
tries the full token as a title, then the session name for window 0/1, then
errors explaining programa has no tmux sessions. Agent hooks failed CLOSED on
session-start/prompt-submit for the same transport errors teardown tolerated —
a quit Programa blocked the next prompt; all three agents now fail open
consistently. focus/close-window no longer report every failure as 'Window not
found', and layout apply sends the caller's cwd as its help text promised.
Audit 2026-08-20 H5. revoke() only removed the device from the trust store, so
an in-progress relay kept full allowlisted access until the phone disconnected
on its own. Admitted connections are now registered per endpointId (under the
existing stateLock, unregistered via defer on every relay exit) and revoke
closes them after clearing trust. Reconnects were already rejected.
…s boundedly

Audit 2026-08-20 H2/M3. TabItemView's == excluded draggedTabId/dropIndicator
while body reads both, so .equatable() froze drag dim and drop indicators
mid-drag — the file's own documented failure mode. The autosave tick recorded
its fingerprint even when the save layer declined (startup restore in flight,
empty snapshot), suppressing up to 60s of identical-content saves after a save
that never happened; declined saves now skip the fingerprint and retry after
1s, capped at 5 consecutive attempts so a windowless app doesn't poll.
…osed-panel shortcut

Audit 2026-08-20 M10/M11. The spec marked M-009/RZ-* DONE while nothing in the
app calls session.* — now DAEMON-ONLY with the integration deferred to the
detached-sessions plan. ⌘⇧T (Reopen closed panel) shipped without a row in the
shortcuts doc, violating the shortcut policy.
@arzafran
arzafran merged commit f75a2f3 into main Aug 20, 2026
10 checks passed
@arzafran
arzafran deleted the fix/audit-2026-08-20-remediation branch August 20, 2026 13:25
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.

1 participant