Skip to content

Keep the commit id in archive names for Windows drive paths - #554

Merged
omer-roth merged 3 commits into
cycodehq:mainfrom
WaleedMahamid:fix/windows-pre-push-commit-id
Oct 1, 2026
Merged

omer-roth merged 3 commits into
cycodehq:mainfrom
WaleedMahamid:fix/windows-pre-push-commit-id

Conversation

@WaleedMahamid

Copy link
Copy Markdown
Contributor

Summary

Fixes support ticket #8417 (on-prem customer, CLI 3.24.2 on Windows): the pre-push secret scan reported "commit_id": "C:" with a drive-less file_path, and the text output crashed with 'NoneType' object has no attribute 'path'.

On Windows a commit-range document's path is absolute by drive letter (C:\repo\file) rather than by a leading separator, so os.path.join(commit_id, path) in concat_unique_id threw the commit id away. The archive entry became C:/repo/file, and from there:

  • the server parsed C: as the commit id and reported the path without the drive;
  • the same file changed in several pushed commits collided in the archive (UserWarning: Duplicate name);
  • the finding could not be matched back to a local document, which crashed the text printer in 3.24.2 (main already keeps a placeholder document since CM-73654, but still misreports the commit id and path).

Changes

  • path_utils.py: new _strip_path_anchor drops the drive (incl. UNC) and leading separators via ntpath.splitdrive, used on every OS. concat_unique_id and normalize_file_path both go through it, so archive naming and result matching agree.
  • Tests for Windows drive and UNC paths in test_zip_file.py, test_scan_result.py, and a new test_path_utils_normalize.py.

Test plan

  • pytest tests passes (one pre-existing, environment-dependent failure in test_codex.py::test_email_from_auth_returns_email, unrelated)
  • ruff check / ruff format --check clean
  • InMemoryZip.append with C:\Users\...\secret-test.txt for two commits yields <sha>/Users/.../secret-test.txt entries with no collision
  • Reproduce the ticket steps on a Windows machine with this branch and confirm the correct commit SHA and full path in -o text and -o json

🤖 Generated with Claude Code

@omer-roth omer-roth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the branch and ran the touched tests locally — 18 passed. The diagnosis in the description is right, and stripping the drive before joining does restore the commit id. The remaining concern is that the fix is still expressed in terms of the host's path rules, so on a real Windows machine the archive entry keeps backslashes and the server would again fail to read the commit id off the first component. Inline suggestions replace the ntpath helper with one that picks a pathlib pure flavor from the running OS and emits POSIX components — that covers the separator problem, the lost UNC share, and the colon-in-a-POSIX-name false positive in one place.

Four things that have no diff line to hang off:

1. The shared OS constant the suggestions import. There are ~8 ad-hoc platform.system() / sys.platform checks scattered around the codebase today (host_info.py, ides/cursor.py, cycode_client_base.py, …). One constant in cycode/cli/consts.py, which has no imports of its own and is already imported by cyclient:

class OperatingSystem(str, Enum):
    WINDOWS = 'windows'
    MACOS = 'macos'
    LINUX = 'linux'


def _detect_operating_system() -> OperatingSystem:
    if sys.platform == 'win32':
        return OperatingSystem.WINDOWS
    if sys.platform == 'darwin':
        return OperatingSystem.MACOS
    return OperatingSystem.LINUX


OPERATING_SYSTEM = _detect_operating_system()

Migrating the existing call sites onto it is out of scope for this PR.

2. get_path_by_os in cycode/cli/apps/scan/scan_result.py:45 becomes dead weight. It flips the server's / to os.sep immediately before normalize_file_path splits the path apart again. Harmless but confusing once both sides normalize to POSIX — worth dropping the call.

3. The Windows box in the test plan is still unchecked. Given the separator point above, that is the box that decides whether the ticket is actually fixed. The suggested tests exercise the Windows rules on Linux CI by passing the flavor explicitly, which is the gap that let this reach a customer in the first place — but one manual run on Windows is still worth doing before merge.

4. Tests for path_utils now live in two places. concat_unique_id is covered in tests/test_zip_file.py while the new normalization cases sit in tests/cli/utils/test_path_utils_normalize.py. Both sets now exercise the same helper, so one home for them would be easier to keep in sync.

One style note on the suggestions themselves: the docstrings in the diff narrate the incident (used to archive the file as 'C:/repo/file', the server then reported 'C:'). That reads well today and badly in a year — the suggestions keep one line on the contract and leave the story to the commit message.

Comment thread cycode/cli/utils/path_utils.py Outdated
Comment thread cycode/cli/utils/path_utils.py Outdated
Comment thread cycode/cli/utils/path_utils.py Outdated
Comment thread cycode/cli/utils/path_utils.py Outdated
Comment thread tests/test_zip_file.py Outdated
Comment thread tests/cli/utils/test_path_utils_normalize.py Outdated
WaleedMahamid-Cycode and others added 2 commits October 1, 2026 15:04
On Windows the file path of a commit-range document is absolute by
drive letter (C:\repo\file), not by a leading separator, so
os.path.join(commit_id, path) threw the commit id away. The archive
entry became C:/repo/file, the server reported "C:" as the commit id
and a drive-less file path, the same file changed in several commits
collided in the archive (zipfile "Duplicate name" warning), and the
text printer could not match the finding to a document.

Strip the drive and the leading separators with ntpath.splitdrive on
every OS before joining, and apply the same normalisation when matching
the server's file names back to local documents.

Reported in support ticket #8417.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Address review: pick the pathlib pure flavor from the running OS instead
of running ntpath.splitdrive everywhere, and emit forward-slash
components. This keeps the UNC server and share in the entry name so
two shares don't collide, stops a colon in a POSIX file name from being
read as a drive, and makes the archive naming and the result matching
agree without relying on zipfile's separator rewrite or get_path_by_os.

Introduce a shared OPERATING_SYSTEM constant in consts (the other
platform checks in the codebase are left for a follow-up), and move all
path_utils tests into tests/cli/utils/test_path_utils.py, with the
Windows cases exercised on POSIX CI by passing the flavor explicitly.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@WaleedMahamid
WaleedMahamid force-pushed the fix/windows-pre-push-commit-id branch from df6065b to c65958f Compare October 1, 2026 12:05
@WaleedMahamid

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review. Applied all five suggestions in c65958f:

  • OperatingSystem enum and OPERATING_SYSTEM constant in consts.py; the other platform checks are left for a follow-up as you scoped it.
  • _to_relative_posix_parts picks the pathlib pure flavor from the running OS and emits POSIX components, so the UNC server and share are kept and a colon in a POSIX name is no longer read as a drive. concat_unique_id and normalize_file_path both build on it; ntpath is gone.
  • Dropped the get_path_by_os call in scan_result.py.
  • Merged the tests into tests/cli/utils/test_path_utils.py, with the Windows cases run on POSIX CI by passing the flavor explicitly, and removed the two old files.
  • Trimmed the docstrings to the contract; the incident story stays in the commit message.

One note on the backslash point: zipfile already rewrites os.sep to / in entry names (that is why the customer's JSON shows forward slashes), so that case was not live on Windows. The UNC and colon cases were real, so the flavor-based design is the right call regardless.

Commits are re-signed with GPG. Could you approve the Actions run so the Windows job in tests.yml covers the real-OS check?

On Windows, normalize_file_path uses the Windows flavor, where 'x:y/creds.txt' is a
drive-relative path and the drive is correctly stripped. Pass the Linux flavor
explicitly so the case asserts the same thing on every CI runner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@WaleedMahamid-Cycode

Copy link
Copy Markdown
Contributor

Comments resolved

@omer-roth
omer-roth merged commit 22ca5c0 into cycodehq:main Oct 1, 2026
26 checks passed
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.

3 participants