You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: resolve Allow/Deny buttons not triggering CLI approval (#106)
* fix: resolve Allow/Deny buttons not triggering CLI approval (farouqaldori#98)
Four fixes addressing the permission approval flow not reaching the CLI:
1. Generate fallback UUID when tool_use_id is missing from PermissionRequest
events, preventing silent socket closure that left UI buttons unable to
send approval back to the CLI.
2. Increase socket read window from 200ms to 2s with early exit on complete
JSON, preventing truncated reads on large tool_input payloads.
3. Flush stdout in Python hook script to ensure hookSpecificOutput JSON
reaches Claude Code without buffering delays.
4. Guard against race condition where Stop/SessionEnd events clean up
permissions before the user has time to respond (2s grace period).
Also includes enhanced diagnostic logging (Swift .warning level for failures,
conditional stderr logging in Python via CLAUDE_ISLAND_DEBUG=1) and fixes
for @inline(always) -> @inline(__always) and .signaled -> .unhandledException
build compatibility issues.
Closesfarouqaldori#98
* fix: resolve CI failures before code review (PR #106)
- Revert TerminationStatus case from `.unhandledException` to `.signaled` to match swift-subprocess 0.4 API (CI resolves latest compatible version)
- Bump swift-subprocess minimum version constraint from 0.2.0 to 0.4.0 to ensure consistent API resolution locally and in CI
* fix: address code review comments (PR #106, iteration 1)
- readClientData: remove 10ms quiet-period early break and require
complete-JSON validation (looksLikeCompleteJSON) before exiting the
poll loop; widen poll slice to 100ms within the existing 2s cap.
Prevents truncation of multi-packet hook payloads that could
reintroduce the "Processing…" hang from farouqaldori#98.
- sendPermissionResponse + sendPermissionResponseBySession: introduce
writeAllBytes helper that loops until all bytes are sent, retries
on EINTR, and waits for POLLOUT on EAGAIN/EWOULDBLOCK within a 2s
deadline. permissionFailureHandler is now only invoked on truly
unrecoverable writes, eliminating spurious failures on partial
writes to the non-blocking socket.
* fix: address code review comments (PR #106, iteration 2)
- claude-island-state.py send_event: replace single sock.recv(4096)
with an EOF-accumulating loop so permission responses that span
multiple kernel reads (normal on SOCK_STREAM; guaranteed when the
`reason` field pushes the JSON past 4KB) are decoded correctly.
The Swift side (writeAllBytes + close) uses EOF as the terminator,
so recv-until-zero is the authoritative contract. Eliminates the
silent fallback to "ask" that caused intermittent Allow/Deny loss.
* fix: address code review comments (PR #106, iteration 3)
- claude-island-state.py send_event: include UnicodeDecodeError in the
except tuple around response.decode()/json.loads. Without it, a
mid-multibyte UTF-8 truncation would crash the hook process and skip
the intended None → "{}"/"ask" fallback in handle_permission_response.
* fix: address code review comments (PR #106, iteration 4)
- HookSocketServer.acceptConnection: update stale "200ms" comment to
reflect readClientData's current 2s cap, so the accept-queue
head-of-line risk is accurately documented.
* fix: address code review comments (PR #106, iteration 5)
- cleanupPendingPermissions: schedule a follow-up cleanup for entries
skipped under the <2s age guard (via queue.asyncAfter) so Stop events
on brand-new permissions no longer leave the Python hook blocked on
its 300s recv. Retry atomically removes the entry only if the
toolUseID still maps to the same sessionID, guarding against session
reuse and double-close races.
- readClientData poll loop: explicitly handle POLLHUP/POLLERR/POLLNVAL
to exit promptly on peer close or invalid fd; previously the loop
spun until the 2s cap when poll returned >0 without POLLIN. POLLIN
is still checked first so pending data is drained before exit.
- writeAllBytes: assign EIO to finalErrno when write() returns 0 (POSIX
doesn't guarantee errno is meaningful in that case), so logs and
diagnostics show a stable code instead of a stale/0 value. Move the
errno capture for negative returns into the n<0 branch where it's
defined to be valid.
* fix: address code review comments (PR #106, iteration 6)
- readClientData: retry read() on EINTR instead of treating it as a
fatal error. Symmetric with the existing poll() EINTR handling;
prevents signal-induced truncation of multi-packet JSON payloads.
- cleanupPendingPermissionsRetry: stop invoking permissionFailureHandler.
Stop hook events are authoritative for session phase (drive to .idle
via determinePhase); letting the deferred retry fire the handler ~2s
later could override .idle back to .waitingForApproval via
SessionStore.processSocketFailure's findNextPendingTool. Both cleanup
paths (main + retry) are now silent FD-close, consistent with Stop
owning phase management. The handler still fires from the genuine
failure sites: handlePermissionSocketDisconnect, cleanupTimedOutPermission,
and sendPermissionResponse* write-failure branches.
* fix: address code review comments (PR #106, iteration 7)
- cleanupPendingPermissions main loop: call markPermissionResponded
alongside the pendingPermissions removal, matching all other
removal sites (cleanupPendingPermissionsRetry, cleanupSpecificPermission,
handlePermissionSocketDisconnect, sendPermissionResponse*). Late
approve/deny responses for a Stop-cancelled session now classify
as .alreadyResponded (quiet) instead of .notFound (warning), and
dedupe behavior is consistent across all paths.
* fix: address code review comments (PR #106, iteration 8)
- claude-island-state.py send_event: make the debug log path resilient
to malformed bytes by using response.decode(errors='replace') in the
_log f-string (which is evaluated eagerly regardless of DEBUG flag).
Pass response bytes directly to json.loads, which handles UTF-8/16/32
BOM detection itself, eliminating the redundant strict decode. Debug
mode can no longer perturb functional parsing. The except tuple with
UnicodeDecodeError is retained as a defensive net.
---------
Co-authored-by: Taylor Watson <taylor.watson@soti.net>
Co-authored-by: engels74 <141435164+engels74@users.noreply.github.com>
0 commit comments