Skip to content

fix(sensor): add Windows path support to CursorParser and ClineParser - #28

Open
Rahul-s-007 wants to merge 1 commit into
uber:mainfrom
Rahul-s-007:fix/windows-path-support
Open

fix(sensor): add Windows path support to CursorParser and ClineParser#28
Rahul-s-007 wants to merge 1 commit into
uber:mainfrom
Rahul-s-007:fix/windows-path-support

Conversation

@Rahul-s-007

Copy link
Copy Markdown
Contributor

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Documentation Update

Related issue: Closes #21

What changed?
CursorParser and ClineParser now check for an %APPDATA%-based Windows install path, added as an elif between the existing macOS check and the final Linux fallback, so macOS still takes precedence when present (regression-guarded by a test). Verified (two independent sources) real Windows path for Cursor: %APPDATA%\Cursor\User\globalStorage\state.vscdb, mirroring the same Electron-app-data convention already hardcoded for macOS/Linux. Cline's Windows path is derived from that same confirmed Cursor root, since it's a VS-Code extension hosted inside Cursor per the existing macOS/Linux paths. Also backfills CursorParser's test coverage, which was previously zero (unlike its sibling parsers), using the same synthetic-SQLite-fixture technique already established for WarpParser. Updates Sensor/README.md's compatibility table for the two affected rows.

Why?
The top-level README.md claims ADR observes agents "on macOS, Linux, and Windows." In practice, neither parser had any Windows branch, so on a Windows machine both silently returned zero entries for two of the most commonly Windows-installed tools this product claims to observe. Confirmed via a full-repo pass that this is a real, isolated gap: ClaudeParser/CodexParser already work on Windows with no branching needed, and ClaudeDesktopParser/WarpParser target apps with no Windows release at all.

How did you test it?
Added 8 new tests (4 per parser): macOS still wins when present (regression guard), Windows selected when APPDATA is set and the path exists, falls back to Linux when nothing exists or APPDATA is unset (no crash), and falls back to Linux when APPDATA is set but the path itself doesn't exist. Plus a CursorParser content-parsing test against a synthetic SQLite fixture. All use monkeypatch on Path.home/APPDATA, no dependency on real local files, per Sensor/CONTRIBUTING.md.

Ran the full suite, pytest tests/ -v:

74 passed, 1 warning (pre-existing, unrelated) — click to expand
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- /private/tmp/adr_sensor_venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/test4/Desktop/OSS/Uber ADR/Sensor
configfile: pyproject.toml
collecting ... collected 74 items

tests/test_cli.py::test_cli_version_matches_package_metadata PASSED      [  1%]
tests/test_observer.py::TestAgentObserver::test_init_default PASSED      [  2%]
tests/test_observer.py::TestAgentObserver::test_display_summary_empty PASSED [  4%]
tests/test_observer.py::TestAgentObserver::test_display_summary_with_entries PASSED [  5%]
tests/test_observer.py::TestAgentObserver::test_save_to_file_json PASSED [  6%]
tests/test_observer.py::TestAgentObserver::test_save_to_file_jsonl PASSED [  8%]
tests/test_observer.py::TestAgentObserver::test_save_sessions_individual PASSED [  9%]
tests/test_observer.py::TestAgentObserver::test_clean_filename PASSED    [ 10%]
tests/test_observer.py::TestAgentObserver::test_filter_entries_by_existing_files PASSED [ 12%]
tests/test_observer.py::TestAgentObserver::test_ingest_all_handles_parser_errors PASSED [ 13%]
tests/test_parsers.py::TestClaudeParser::test_parse_jsonl_file PASSED    [ 14%]
tests/test_parsers.py::TestClaudeParser::test_parse_empty_file PASSED    [ 16%]
tests/test_parsers.py::TestClaudeParser::test_parse_all_no_directory PASSED [ 17%]
tests/test_parsers.py::TestClaudeParser::test_truncate_large_arguments PASSED [ 18%]
tests/test_parsers.py::TestCursorParser::test_prefers_macos_path_even_when_appdata_also_has_one PASSED [ 20%]
tests/test_parsers.py::TestCursorParser::test_selects_windows_path_when_appdata_set_and_exists PASSED [ 21%]
tests/test_parsers.py::TestCursorParser::test_falls_back_to_linux_path_when_nothing_exists PASSED [ 22%]
tests/test_parsers.py::TestCursorParser::test_falls_back_to_linux_when_appdata_set_but_path_missing PASSED [ 24%]
tests/test_parsers.py::TestCursorParser::test_parse_all_no_database PASSED [ 25%]
tests/test_parsers.py::TestCursorParser::test_parses_conversation_content PASSED [ 27%]
tests/test_parsers.py::TestClineParser::test_prefers_macos_path_even_when_appdata_also_has_one PASSED [ 28%]
tests/test_parsers.py::TestClineParser::test_selects_windows_path_when_appdata_set_and_exists PASSED [ 29%]
tests/test_parsers.py::TestClineParser::test_falls_back_to_linux_path_when_nothing_exists PASSED [ 31%]
tests/test_parsers.py::TestClineParser::test_falls_back_to_linux_when_appdata_set_but_path_missing PASSED [ 32%]
tests/test_parsers.py::TestClineParser::test_parse_cline_log PASSED      [ 33%]
tests/test_parsers.py::TestClineParser::test_extract_mcp_tools PASSED    [ 35%]
tests/test_parsers.py::TestClineParser::test_parse_no_directory PASSED   [ 36%]
tests/test_parsers.py::TestCodexParser::test_parse_jsonl_file PASSED     [ 37%]
tests/test_parsers.py::TestCodexParser::test_parse_no_directory PASSED   [ 39%]
tests/test_parsers.py::TestWarpParser::test_skips_conversations_older_than_max_age PASSED [ 40%]
tests/test_parsers.py::TestWarpParser::test_all_history_via_large_max_age_days PASSED [ 41%]
tests/test_parsers.py::TestWarpParser::test_parses_conversation_content PASSED [ 43%]
tests/test_parsers.py::TestWarpParser::test_parse_all_no_database PASSED [ 44%]
tests/test_parsers.py::TestWarpParser::test_default_max_age_days PASSED  [ 45%]
tests/test_schemas.py::TestToolUsage::test_create_basic PASSED           [ 47%]
tests/test_schemas.py::TestToolUsage::test_create_with_all_fields PASSED [ 48%]
tests/test_schemas.py::TestToolUsage::test_frozen PASSED                 [ 50%]
tests/test_schemas.py::TestChatMessage::test_create_user_message PASSED  [ 51%]
tests/test_schemas.py::TestChatMessage::test_create_assistant_message_with_tools PASSED [ 52%]
tests/test_schemas.py::TestAgentEvent::test_create_basic PASSED          [ 54%]
tests/test_schemas.py::TestAgentEvent::test_uuid_deterministic PASSED    [ 55%]
tests/test_schemas.py::TestAgentEvent::test_uuid_unique_for_different_sessions PASSED [ 56%]
tests/test_schemas.py::TestAgentEvent::test_has_meaningful_content_empty PASSED [ 58%]
tests/test_schemas.py::TestAgentEvent::test_has_meaningful_content_single_error PASSED [ 59%]
tests/test_schemas.py::TestAgentEvent::test_has_meaningful_content_real_conversation PASSED [ 60%]
tests/test_schemas.py::TestAgentEvent::test_has_meaningful_content_with_tools PASSED [ 62%]
tests/test_schemas.py::TestAgentEvent::test_has_tool_usage PASSED        [ 63%]
tests/test_schemas.py::TestAgentEvent::test_to_dict PASSED               [ 64%]
tests/test_schemas.py::TestAgentEvent::test_to_json PASSED               [ 66%]
tests/test_schemas.py::TestAgentEvent::test_get_summary PASSED           [ 67%]
tests/test_schemas.py::TestAgentEvent::test_get_content_hash PASSED      [ 68%]
tests/test_schemas.py::TestAgentEvent::test_get_non_null_fields PASSED   [ 70%]
tests/test_schemas.py::TestAgentEvent::test_auto_populates_hostname_username PASSED [ 71%]
tests/test_utils.py::TestTruncateMiddle::test_short_string_unchanged PASSED [ 72%]
tests/test_utils.py::TestTruncateMiddle::test_empty_string PASSED        [ 74%]
tests/test_utils.py::TestTruncateMiddle::test_none_string PASSED         [ 75%]
tests/test_utils.py::TestTruncateMiddle::test_exact_length_unchanged PASSED [ 77%]
tests/test_utils.py::TestTruncateMiddle::test_truncation PASSED          [ 78%]
tests/test_utils.py::TestTruncateMiddle::test_custom_edge_chars PASSED   [ 79%]
tests/test_utils.py::TestNormalizeTimestamp::test_iso_string PASSED      [ 81%]
tests/test_utils.py::TestNormalizeTimestamp::test_iso_string_with_offset PASSED [ 82%]
tests/test_utils.py::TestNormalizeTimestamp::test_unix_timestamp_seconds PASSED [ 83%]
tests/test_utils.py::TestNormalizeTimestamp::test_unix_timestamp_milliseconds PASSED [ 85%]
tests/test_utils.py::TestNormalizeTimestamp::test_datetime_naive PASSED  [ 86%]
tests/test_utils.py::TestNormalizeTimestamp::test_datetime_aware PASSED  [ 87%]
tests/test_utils.py::TestNormalizeTimestamp::test_string_unix_timestamp PASSED [ 89%]
tests/test_utils.py::TestNormalizeTimestamp::test_invalid_string PASSED  [ 90%]
tests/test_utils.py::TestNormalizeTimestamp::test_invalid_type PASSED    [ 91%]
tests/test_utils.py::TestNormalizeTimestamp::test_float_timestamp PASSED [ 93%]
tests/test_utils.py::TestFormatTimestampForFilename::test_format PASSED  [ 94%]
tests/test_utils.py::TestParseTimestampFromFilename::test_valid_filename PASSED [ 95%]
tests/test_utils.py::TestParseTimestampFromFilename::test_invalid_filename PASSED [ 97%]
tests/test_utils.py::TestParseTimestampFromFilename::test_none_filename PASSED [ 98%]
tests/test_utils.py::TestParseTimestampFromFilename::test_malformed_timestamp PASSED [100%]

=============================== warnings summary ===============================
tests/test_observer.py::TestAgentObserver::test_ingest_all_handles_parser_errors
  /Users/test4/Desktop/OSS/Uber ADR/Sensor/adr_sensor/observer.py:66: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
    "timestamp": datetime.utcnow().isoformat(timespec="milliseconds") + "Z",

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================== 74 passed, 1 warning in 0.16s =========================

Potential risks
Low. The Windows branch only activates on platforms where APPDATA is set and the specific path exists, on today's macOS/Linux CI and dev machines, os.environ.get("APPDATA") returns None and the new code path is a no-op, falling straight through to the existing Linux fallback exactly as before.

The top-level README claims ADR observes agents "on macOS, Linux, and
Windows," but CursorParser and ClineParser only checked macOS
(Library/Application Support/...) and Linux (.config/...) install
paths. On a Windows machine neither exists, so both parsers silently
fall through to the (also nonexistent) Linux path and return zero
entries - a blind spot for two of the most commonly Windows-installed
tools this product claims to observe. Sensor/README.md's own
compatibility table was honest about the "macOS, Linux" only ceiling,
so this closes a real, disclosed gap rather than fixing a doc lie.

Adds an %APPDATA%-based Windows branch to both parsers' path-detection
logic, preserving macOS-first precedence. Verified (two independent
sources) Windows path for Cursor: %APPDATA%\Cursor\User\globalStorage\
state.vscdb - the standard Electron-app-data convention already
mirrored by the existing macOS/Linux split. Cline is a VS-Code
extension hosted inside Cursor (per the existing paths targeting
.../Cursor/User/globalStorage/saoudrizwan.claude-dev/tasks), so its
Windows path follows the same confirmed root.

No shared helper introduced: Sensor/CONTRIBUTING.md says to keep
parsers self-contained, the existing code already duplicates the
macOS/Linux logic per-parser with no abstraction, and the new logic is
~4 lines/parser.

Confirmed via a full-repo pass that no Windows path handling exists
anywhere else in Sensor/adr_sensor/. The other four parsers don't have
this gap: ClaudeParser/CodexParser already use bare Path.home()-relative
paths that resolve correctly on Windows with no branching needed, and
ClaudeDesktopParser/WarpParser target apps with no Windows release to
begin with.

Also backfills CursorParser's test coverage (previously zero, unlike
its sibling parsers) with a basic content-parsing test using the same
synthetic-SQLite-fixture technique already established for WarpParser.

Updates Sensor/README.md's compatibility table for the two affected
rows. No change needed to the top-level README's platform claim - it
becomes accurate for these two sources.

Fixes uber#21

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

CursorParser and ClineParser have no Windows path support, silently returning zero telemetry on Windows

1 participant