Skip to content

Use handle-based deletion in FileHelper - #5352

Open
mnriem wants to merge 19 commits into
github:mainfrom
mnriem:mnriem-file-helper-deletion-handles
Open

mnriem wants to merge 19 commits into
github:mainfrom
mnriem:mnriem-file-helper-deletion-handles

Conversation

@mnriem

@mnriem mnriem commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Description

Use directory handles for FileHelper.delete() and recursive enumeration, preserving contained parent symlinks, trusted root aliases, and leaf-link deletion that removes the link rather than its target. Share the existing Windows handle primitive with catalog-cache I/O, add deletion and handle-lifetime coverage, and update the FileHelper contract.

Testing

Set up this checkout's .venv and editable install using the local development guide. Run commands from the checkout root.

  • Tested locally with .venv/bin/specify --help (Windows PowerShell: .\.venv\Scripts\specify.exe --help)
  • Ran relevant existing tests with .venv/bin/python -m pytest tests -q (Windows PowerShell: .\.venv\Scripts\python.exe -m pytest tests -q); use affected test paths for focused checks and LC_ALL=en_US.UTF-8 on macOS
  • Tested with a sample project (if applicable)

.venv/bin/specify --help — passed.

Focused command:

.venv/bin/python -m pytest tests/test_file_helper.py tests/test_file_handles.py tests/specify_cli/workflows/test_catalog_cache.py tests/integrations/test_integration_state.py tests/specify_cli/test_file_policy.py tests/specify_cli/workflows/test_command_list.py tests/specify_cli/workflows/test_operation_list.py tests/test_presets.py -q

Result: 599 passed, 82 skipped on macOS. The full suite was not run. Native Windows handle-sharing behavior was not exercised; portable Windows handle-contract checks passed. Sample-project testing is not applicable to this filesystem-helper change.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: GitHub Copilot, powered by GPT-6.1 Sol (gpt-6.1-sol), performed the implementation, test generation and execution, documentation changes, and commit/PR preparation on behalf of @mnriem in autonomous mode. The reasoning-effort setting was not exposed to the agent.

Anchor recursive enumeration and entry removal to directory handles while preserving supported symlink behavior. Share the Windows handle primitive and document deletion semantics.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 9, 2026 22:45
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Nov 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

An ancestor-symlink race can redirect deletion outside the trusted root, while requiring read access to every directory also breaks deletion from write-and-execute-only parents.

2 open issues
What changed in this PR

This PR moves FileHelper deletion to directory-handle-based access, aiming to prevent parent-link swaps while preserving existing symlink behavior.

Changes:

  • Anchor recursive enumeration and removal using POSIX directory descriptors or Windows ancestor handles.
  • Share the Windows handle primitive between FileHelper and catalog-cache I/O.
  • Add deletion-race and handle-cleanup coverage, and document the safety guarantees and platform limits.

AI disclosure: GitHub Copilot performed autonomous review and focused checks for the requester; model and reasoning settings were unavailable.

File Description
tests/​test_file_helper.py Adds deletion-swap, descriptor-cleanup, Windows handle-lifetime, and root-alias tests.
tests/​test_file_handles.py Adds portable Windows handle validation and cleanup checks.
src/​specify_cli/​workflows/​_catalog_cache.py Imports the shared Windows handle primitive.
src/​specify_cli/​file_helper.py Anchors recursive scans and removals to directory handles.
src/​specify_cli/​_file_handles.py Extracts the reusable Windows handle context manager.
design/​file-helper.md Defines handle-based deletion guarantees and remaining safety limits.

Review effort: Max (auto)

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Reuse one identity-checked root anchor throughout deletion. Use search-only POSIX descriptors for traversal and request read access only for recursive enumeration. Keep Windows canonical ancestors held for the operation.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 9, 2026 23:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A critical POSIX symlink race can delete an unintended file because path validation uses a replacement tree while removal uses the original root.

1 open issue
2 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/file_helper.py Resolved
Use descriptor-relative metadata and link reads in the existing path walker for POSIX deletion. Keep initial validation, recursive classification, and per-entry checks on the same retained directory tree as removal, while preserving supported symlink behavior.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 9, 2026 23:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Deletion can target an attacker-replaced root after inode reuse, directory-open costs grow cubically with tree depth, and a permission test fails on POSIX hosts lacking both O_SEARCH and O_PATH.

1 open issue
2 previously missed issues

In code that hasn't changed since last review

High severity Pin the root directory handle to prevent replacement

src/​specify_cli/​file_helper.py:280

If an attacker can remove and recreate the root between helper construction and delete(), inode reuse lets a replacement pass samestat, so deletion can operate on a different directory tree. Keep the established root pinned with a retained handle and explicit cleanup rather than relying solely on stored device and inode numbers.

High severity Skip permission test when search-only flags are unavailable

tests/​test_file_helper.py:889

This test assumes deletion works through a write-and-execute-only directory, but _deletion_flags() intentionally falls back to O_RDONLY on POSIX hosts without O_SEARCH or O_PATH. On those hosts, the unreadable parent causes PermissionError, so the test fails despite matching the documented fallback. Skip this case when neither flag is available.

1 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/file_helper.py Resolved
Keep helper roots pinned until explicit close or context exit, and close bootstrap consumers deterministically. Carry parent descriptors through component resolution and both recursive deletion passes. Cover root cleanup, bounded directory opens, and POSIX backends without search-only flags.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 10, 2026 13:58
Preserve upstream exclusive-creation behavior and handle-prototype coverage alongside retained-root deletion and deterministic helper cleanup.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Outstanding regressions can block ordinary helper operations on Windows, cause deep-tree deletion to raise RecursionError, and leave root descriptors and Windows ancestor locks open on workflow error paths.

7 open issues
1 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/_file_policy.py Resolved
Comment thread src/specify_cli/_file_policy.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Comment thread tests/test_file_helper.py Resolved
Copilot AI auto review requested due to automatic review settings November 10, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread design/file-helper.md Resolved
Comment thread src/specify_cli/_file_policy.py Resolved
Comment thread src/specify_cli/integration_state.py Resolved
Share Darwin search-only flags, preserve deep deletion planning, and keep Windows root identity references distinct from operation-time ancestry pins. Close workflow owners on every exit and cover constructor diagnostics, retained-error cleanup, recursive handles, and overwrite failures.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 10, 2026 15:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Compare metadata from the borrowed Windows root handle with the initial root observation. Use a temporary non-inheritable duplicate for native fstat without closing the original reference. Raise fresh copies of saved capability errors and cover acquisition restoration, error independence, and duplicate cleanup.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 10, 2026 16:28
Restore OSError.winerror explicitly after shallow copying, since Windows omits that built-in slot from its copy protocol. Add a portable regression for lossy copies while keeping independent traceback ownership.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread tests/test_file_helper.py Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed to evaluate an unpublished recursive-deletion regression that can exhaust file descriptors on deeply nested directory trees.

No open issues 🎉

1 resolved since last review

Review effort: Max (auto)

Use explicit preflight and postorder worklists with bounded directory caches. Retain the selected parent and root, reopen evicted prefixes without following links, and preserve complete preflight and Windows ancestry locks. Cover both recursive phases under a 64-descriptor process limit, cache eviction, cleanup, and measured open-work budgets.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 10, 2026 21:19
Combine upstream relative Windows read handles and shared POSIX I/O with deletion's retained root references and fixed recursive descriptor budget. Preserve both handle contracts and scope operation mocks after helper construction.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed because an unpublished compatibility finding indicates cached file-identity query failures can block every deletion on FAT32, exFAT, and some SMB volumes.

No open issues 🎉

Review effort: Max (auto)

Copilot AI auto review requested due to automatic review settings November 10, 2026 21:46
Prefer tagged extended IDs and use reliable legacy volume/index identities when the filesystem does not support FileIdInfo. Keep the retained root reference, query both live handles using the selected scheme, and preserve native capability and permission failures. Cover fallback selection, zero/ambiguous IDs, rename/reused-index cases, and native Windows legacy APIs.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

On Windows, reparse-point changes after preflight can redirect pathname-based checks and deletions outside the root even when symlinks are disallowed.

2 open issues

Review effort: Max (auto)

Comment thread src/specify_cli/file_helper.py Resolved
Comment thread src/specify_cli/file_helper.py Resolved
Copilot AI auto review requested due to automatic review settings November 10, 2026 22:11
Release HTTP error responses deterministically before raising catalog errors so delayed cleanup cannot leak resources or warnings into later tests. Preserve transient-status fallback, definitive client errors, original exception causes, and already-closed response behavior.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mnriem
mnriem requested an auto review from Copilot and removed request for Copilot November 10, 2026 22:35
Carry directory handles through entry classification, link reads, enumeration and removal. Mark the classified leaf handle for deletion instead of reopening its pathname, preserving supported links and explicit native errors. Cover in-place junction changes and fix operation cleanup tests so constructor calls cannot satisfy their fstat failures.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Two fstat failure-injection tests now pass on constructor errors without reaching the read/overwrite checks, silently losing their intended descriptor-cleanup coverage.

2 open issues

Review effort: Max (auto)

Copilot AI auto review requested due to automatic review settings November 10, 2026 22:57
Hook parent replacement at native entry disposition and enumeration instead of pathname functions that Windows deletion no longer uses. Simulate reparse metadata through native handle queries and retain actual acquired-parent and final-validation coverage.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mnriem
mnriem requested an auto review from Copilot and removed request for Copilot November 10, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Recursive Windows deletion opens directory handles without the required synchronization flags, risking enumeration failures or buffer reads before I/O completion.

1 open issue
2 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/_file_handles.py Resolved
Request FILE_SYNCHRONOUS_IO_NONALERT together with SYNCHRONIZE for relative directory opens, preserving existing type and no-follow flags. Update native ABI assertions and cover the actual Windows directory handle mode and enumeration contract.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 10, 2026 23:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Human assessment is needed to evaluate a Windows recursive-deletion failure involving delete-pending children with open share-delete handles.

No open issues 🎉

1 resolved since last review

Review effort: Max (auto)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-must-have Verdict: high-value, important work for Spec Kit — do first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants