Skip to content

fix: Windows compatibility for stdin reading in hooks#10

Open
TechDufus wants to merge 1 commit intomainfrom
fix/windows-sigalrm-compat
Open

fix: Windows compatibility for stdin reading in hooks#10
TechDufus wants to merge 1 commit intomainfrom
fix/windows-sigalrm-compat

Conversation

@TechDufus
Copy link
Owner

Problem

All three SessionStart hooks fail on Windows because hook_utils.py uses two Unix-only APIs:

  1. signal.SIGALRM — does not exist on Windows (AttributeError)
  2. select.select() on stdin — not supported on Windows for non-socket file descriptors (OSError)

This causes the read_stdin_safe() function to crash before any hook logic runs, producing the startup hook errors reported in #9.

Fix

Refactored read_stdin_safe() into platform-specific implementations:

  • Unix (_read_stdin_unix): Existing select() + SIGALRM approach — unchanged behavior
  • Windows (_read_stdin_windows): Uses a daemon thread with join(timeout) as a cross-platform timeout mechanism

Detection is done once at module load via _HAS_SIGALRM = hasattr(signal, 'SIGALRM').

Testing

  • Unix behavior is identical (no code path changes)
  • Windows path uses stdlib only (threading) — no new dependencies
  • The daemon thread approach ensures the hook process won't hang if stdin blocks

Fixes #9

@TechDufus TechDufus force-pushed the fix/windows-sigalrm-compat branch 2 times, most recently from 006a34b to 3f8f2a0 Compare February 7, 2026 23:29
signal.SIGALRM and select() on stdin are Unix-only. On Windows,
both raise errors causing all SessionStart hooks to fail.

Refactored read_stdin_safe() into platform-specific implementations:
- Unix: existing select() + SIGALRM approach (unchanged behavior)
- Windows: daemon thread with join(timeout) fallback

Fixes #9
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Windows failures in the oh-my-claude hooks by making stdin reads tolerant of Windows’ lack of signal.SIGALRM and select.select() support on non-socket stdin, preventing startup hooks from crashing before hook logic runs.

Changes:

  • Refactors read_stdin_safe() into a Unix implementation (select + SIGALRM) and a Windows implementation (daemon thread + join(timeout)).
  • Adds _HAS_SIGALRM module-level detection to select the appropriate implementation at runtime.
  • Updates read_stdin_safe() docstring to describe the platform-specific behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +129 to +132
"""Windows stdin reader using threading timeout.

Windows lacks SIGALRM and select() on stdin, so we use a
thread-based approach with msvcrt for non-blocking reads.
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows implementation docstring says it uses “msvcrt for non-blocking reads”, but the code only uses a background thread + sys.stdin.read(). Update the docstring to match the actual mechanism (or add the msvcrt-based non-blocking read if that’s intended).

Suggested change
"""Windows stdin reader using threading timeout.
Windows lacks SIGALRM and select() on stdin, so we use a
thread-based approach with msvcrt for non-blocking reads.
"""Windows stdin reader using a background thread and timeout.
Windows lacks SIGALRM and select() on stdin, so we use a
thread-based approach that wraps a blocking sys.stdin.read()
call and enforces a timeout and maximum size.

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +188
def read_stdin_safe(
timeout: int = STDIN_TIMEOUT_SECONDS,
max_bytes: int = MAX_STDIN_BYTES,
) -> str:
"""
Safely read from stdin with timeout and size limits.

On Unix: uses select() for non-blocking check with SIGALRM as backup.
On Windows: uses a daemon thread with join timeout.
Returns empty string on timeout for graceful degradation.

Args:
timeout: Maximum seconds to wait for input.
max_bytes: Maximum bytes to read.

Returns:
Content read from stdin, or empty string on timeout.

Raises:
StdinSizeError: If input exceeds max_bytes.
"""
if _HAS_SIGALRM:
return _read_stdin_unix(timeout, max_bytes)
else:
return _read_stdin_windows(timeout, max_bytes)
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read_stdin_safe() now has platform-specific behavior (SIGALRM/select on Unix, thread timeout on Windows), but the existing hook_utils test suite doesn’t cover stdin timeout/size-limit behavior. Adding tests (e.g., patching sys.stdin with StringIO for size limit and simulating timeout by patching the platform selector or the join call) would help prevent regressions—especially for the Windows path that motivated this PR.

Copilot uses AI. Check for mistakes.
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.

[BUG] SessionStart:startup hook error

1 participant