PowerShell 5.1 (used by IME) reads scripts without BOM as ANSI,
corrupting UTF-8 multi-byte chars (em-dashes, Unicode symbols) and
causing cascading parse errors that prevented agent installation.
Replaced all non-ASCII characters with ASCII equivalents in both
Install and Uninstall bootstrap scripts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-review fixes for the EnrollmentTracker locking commit:
- NotifyDesktopArrived: move _logger.Debug outside lock (no I/O under lock)
- CollectDeviceInfoAtFinalizingSetup: same pattern
- EmitEnrollmentFailed: wrap _stateData.SignalsSeen copy in lock via
SnapshotSignalsSeen() to prevent InvalidOperationException if
RecordSignal modifies the list concurrently
- WriteFinalStatus: same fix for SignalsSeen access
https://claude.ai/code/session_01EWzmYXFmXUaPpeksi6WGKZ
All mutable state fields accessed from multiple threads (timer callbacks,
ImeLogTracker events, EspAndHelloTracker events, desktop detection,
background Task.Run) are now protected by a single _stateLock.
Key fixes:
- TryEmitEnrollmentComplete: atomic guard-check-and-claim prevents double
enrollment_complete emission (the most critical race condition)
- RecordSignal: List<string> mutations are now thread-safe
- NotifyDesktopArrived: atomic check-and-set prevents duplicate processing
- CollectDeviceInfo: _enrollmentType/_skipUserStatusPage writes are visible
to concurrent readers immediately after lock release
- OnHelloCompleted/OnWaitingForHelloSafetyTimeout: consistent multi-field
reads prevent wrong branch selection
- SummaryTimerCallback: _stateDirty flag and _stateData serialization are
synchronized to prevent corrupted state persistence
- EmitAppTrackingSummaryIfChanged: hash comparison is atomic to prevent
duplicate summary events
Lock discipline: all lock sections contain ONLY field reads/writes — no
_emitEvent, _logger, timer operations, or external callbacks. This
guarantees no deadlocks and microsecond-level contention.
https://claude.ai/code/session_01EWzmYXFmXUaPpeksi6WGKZ
When CorrelateAsync returned null (zero findings after correct version
filtering), the rescan code path fell through to returning the OLD
stored report instead of persisting the new result. Now properly
handles the null case by deleting the stale report and returning
a clean response.
Added DeleteVulnerabilityReportAsync to the repository interface
and implementations.
https://claude.ai/code/session_01Kv2aAEVqbeFSsyPv7gU7pF
The session ID is generated inside StreamableHTTPServerTransport during
handleRequest() when it processes the initialize method. The previous
code called transports.set() BEFORE handleRequest(), at which point
transport.sessionId was still undefined — so the session was never
stored in the map. Every subsequent request got "No valid session."
https://claude.ai/code/session_01S8iM5fK53kYa54tydpgsNq