Skip to content

fix runtime error printing #1129

Merged
spoorcc merged 15 commits into
mainfrom
claude/fix-runtime-error-printing-Vayam
Jun 14, 2026
Merged

fix runtime error printing #1129
spoorcc merged 15 commits into
mainfrom
claude/fix-runtime-error-printing-Vayam

Conversation

@spoorcc

@spoorcc spoorcc commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Improvements
    • Runtime errors are now reported immediately per failing project (project-scoped context) across update-related commands, and processing continues for remaining projects.
    • Enhanced spinner/status handling and added more debug output for environment checks and SVN externals indexing.
  • Bug Fixes
    • Improved glob safety by resolving the root once for safer path comparisons.
  • Tests
    • Updated fuzzing inputs and refreshed expected CLI/feature output formatting.
  • Documentation
    • Improved Sphinx scenario inclusion rendering and LaTeX/PDF emoji handling.
  • Chores
    • Updated changelog entry and configuration patterns.

@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@spoorcc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 30 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1af5fd8d-0f12-42d7-802b-0b82b144e44f

📥 Commits

Reviewing files that changed from the base of the PR and between 716ec0d and c80559e.

📒 Files selected for processing (22)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/environment.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/log.py
  • dfetch/project/svnsubproject.py
  • dfetch/util/util.py
  • dfetch/vcs/svn.py
  • doc/_ext/scenario_directive.py
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/updated-project-has-dependencies.feature
  • tests/test_fuzzing.py

Walkthrough

This PR refactors error handling across dfetch commands to provide per-project error context instead of accumulating and displaying errors at the end. The catch_runtime_exceptions utility is removed and replaced with inline try/except RuntimeError blocks that log warnings immediately for each failing project. New logging helpers support per-project error reporting. Feature tests are updated to validate the new output format. Additionally, two Sphinx documentation extensions are enhanced for improved PDF/LaTeX rendering and builder-agnostic scenario include handling.

Changes

Per-Project Error Handling Refactor

Layer / File(s) Summary
Logging infrastructure for error reporting
dfetch/log.py
DLogger gains _active_status class state to track the current Rich Status spinner, update_status(message) to refresh the spinner text, and print_error_line(name, info) to emit red-marked project-specific error lines with name-once deduplication.
Utility cleanup and glob robustness
dfetch/util/util.py
catch_runtime_exceptions context manager is removed entirely. glob_within_root is improved to resolve the root path once and use that resolved path for relative-path checks, preventing symlink inconsistencies.
Check command refactored
dfetch/commands/check.py
Import changed from catch_runtime_exceptions to in_directory. Exception handling loop now uses per-project try/except RuntimeError with immediate error logging and a had_errors flag instead of collecting exceptions into a list.
Diff command with new helper
dfetch/commands/diff.py
Imports updated to include SuperProject and dfetch.manifest.project. New _diff_project() method encapsulates per-project diff logic: validates destination, resolves subproject, computes patch, and writes output. Main loop refactored to try/except per-project with error logging.
Format-patch command refactored
dfetch/commands/format_patch.py
Replaces catch_runtime_exceptions with had_errors boolean tracking and per-project try/except RuntimeError blocks. Failures logged via print_error_line(); a bare RuntimeError raised only if any project failed.
Freeze command refactored
dfetch/commands/freeze.py
Removes catch_runtime_exceptions import and introduces had_errors flag with per-project try/except RuntimeError blocks. Failures logged via print_error_line(); manifest dumping remains guarded by the separate manifest_updated flag.
Update command refactored
dfetch/commands/update.py
Destination sanity checks and sub-project updates moved into separate try/except RuntimeError blocks. Failures logged via print_error_line() and tracked with had_errors; a bare RuntimeError raised only if any project failed.
Update-patch command with new helper
dfetch/commands/update_patch.py
Per-project patch update workflow extracted into _process_project() helper, converting early continues to returns. Main loop wraps helper call in try/except RuntimeError with per-project error logging via print_error_line().
VCS and environment logging enhancements
dfetch/project/svnsubproject.py, dfetch/vcs/svn.py, dfetch/commands/environment.py
SvnSubProject updates spinner status during external indexing. SvnRepo adds debug logging for recursive externals scanning with revision suffix. Environment command adds debug logging before version check.
Feature test output expectations
features/check-git-repo.feature, features/check-svn-repo.feature, features/fetch-archive.feature, features/fetch-checks-destination.feature, features/patch-after-fetch-git.feature, features/patch-after-fetch-svn.feature, features/updated-project-has-dependencies.feature
Eight BDD scenarios updated to expect error messages nested under project-name headers with indented quoted output (using > prefix), matching the new per-project error logging format.
Documentation and configuration
CHANGELOG.rst, .claude/settings.json, tests/test_fuzzing.py
Release 0.14.0 (unreleased) changelog documents the runtime error display change. Configuration file updated with refined Git-related command allowances. Fuzzing strategy SAFE_VERSION narrowed to generate only integers and finite floats for manifest version field.

Documentation Build System Enhancements

Layer / File(s) Summary
Colordot Sphinx extension with LaTeX emoji replacement
doc/_ext/colordot.py
Extension now includes PDF/LaTeX support by replacing coloured-circle emoji with TikZ-drawn circles during doctree-resolved phase. New emoji-to-TikZ mapping, regex detection, and text node splitting logic added. Extension version bumped from 0.1 to 0.2.
Scenario directive extension with builder-agnostic rendering
doc/_ext/scenario_directive.py
Refactored to use builder-agnostic ScenarioIncludePlaceholder nodes deferred to doctree-resolved phase. New resolve_scenario_include_placeholders() handler renders inline HTML details-blocks for HTML/inline builds and appendix references for PDF/LaTeX builds. Helper functions extract appendix metadata registration and feature path resolution. Extension version bumped from 0.3 to 0.4.

Sequence Diagram(s)

sequenceDiagram
  participant Command as Diff command loop
  participant Helper as _diff_project
  participant SuperProject
  participant Filesystem

  Command->>Helper: try _diff_project(superproject, project)
  Helper->>Helper: validate destination exists
  Helper->>SuperProject: get_sub_project(project.name)
  Helper->>Helper: derive old_rev from metadata if needed
  Helper->>SuperProject: diff(subproject, old_rev, new_rev)
  SuperProject-->>Helper: patch content
  Helper->>Filesystem: write project.patch if non-empty
  Helper-->>Command: return (success) or RuntimeError
  Command->>Command: except: log error, set had_errors, continue
Loading
sequenceDiagram
  participant Command as UpdatePatch command loop
  participant Helper as _process_project
  participant SuperProject
  participant PatchFile

  Command->>Helper: try _process_project(superproject, project)
  Helper->>Helper: validate preconditions (patch exists, fetched, no local changes)
  Helper->>SuperProject: metadata_force_update
  Helper->>SuperProject: generate reverse diff
  Helper->>PatchFile: _update_patch with reverse diff
  Helper->>SuperProject: metadata_force_update to apply patch
  Helper-->>Command: return (success) or RuntimeError
  Command->>Command: except: log error, set had_errors, continue
Loading
flowchart TD
  A["ScenarioIncludeDirective.run()"]
  B["Register appendix entry metadata"]
  C["Emit ScenarioIncludePlaceholder node"]
  D["doctree-resolved phase"]
  E{"HTML or inline build?"}
  F["Render inline HTML details block"]
  G{"PDF/LaTeX build?"}
  H["Emit ScenarioAppendixRef node"]
  I["process_scenario_appendix<br/>builds grouped appendix sections"]

  A --> B
  B --> C
  C --> D
  D --> E
  E -->|Yes| F
  E -->|No| G
  G -->|Yes| H
  H --> I
  F --> I
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dfetch-org/dfetch#928: Adjustment to Hypothesis SAFE_VERSION strategy is directly related to the fuzzing test suite introduced in that PR.
  • dfetch-org/dfetch#960: Per-project error handling via DLogger helpers (print_error_line, update_status) depends on the terminal/logging enhancements introduced in that PR.
  • dfetch-org/dfetch#1107: Both PRs modify dfetch/commands/freeze.py's control flow for per-project error handling and manifest editing.

Suggested labels

bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix runtime error printing' is vague and does not clearly convey the specific nature of the changes. While it relates to runtime errors, the changeset involves a significant refactoring of error handling across multiple commands (check, diff, format_patch, freeze, update, update_patch) from collecting exceptions to per-project inline error handling with structured logging. Consider a more descriptive title such as 'Refactor runtime error handling to print per-project errors inline' or 'Change error reporting from collected exceptions to per-project inline messages' to better communicate the scope and approach of the changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-runtime-error-printing-Vayam

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dfetch/commands/freeze.py (1)

87-111: ⚠️ Potential issue | 🟡 Minor

Inconsistent error handling pattern compared to other commands.

Unlike check.py, update.py, diff.py, and update_patch.py, this command does not track had_errors or raise RuntimeError() at the end when projects fail. The warning is logged, but the command exits successfully even if some projects failed to freeze.

If this is intentional (e.g., to allow partial freezes), consider adding a comment explaining the design choice. Otherwise, consider aligning with the pattern used in other commands:

🔧 Proposed fix to align with other commands
     def __call__(self, args: argparse.Namespace) -> None:
         """Perform the freeze."""
         del args  # unused
 
         superproject = create_super_project()
 
         projects: list[ProjectEntry] = []
+        had_errors: bool = False
 
         with in_directory(superproject.root_directory):
             for project in superproject.manifest.projects:
                 try:
                     # ... existing logic ...
                     projects.append(project)
                 except RuntimeError as exc:
                     logger.print_warning_line(project.name, str(exc))
+                    had_errors = True
 
             # ... manifest creation and dump ...
+
+        if had_errors:
+            raise RuntimeError()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/freeze.py` around lines 87 - 111, The freeze command
currently logs RuntimeError per-project but does not set/fail the overall
command like other commands (check.py, update.py, diff.py, update_patch.py);
update the freeze implementation (the loop that calls
dfetch.project.create_sub_project(...), sub_project.freeze_project(...), and the
except RuntimeError as exc block) to follow the same pattern: introduce/maintain
a had_errors flag, set had_errors = True when catching a RuntimeError, and after
processing all projects raise RuntimeError() (or rethrow) if had_errors is True
so the command exits non‑zero; if the current behavior is intentional, add a
brief explanatory comment above that try/except to document why partial failures
should not fail the command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@dfetch/commands/freeze.py`:
- Around line 87-111: The freeze command currently logs RuntimeError per-project
but does not set/fail the overall command like other commands (check.py,
update.py, diff.py, update_patch.py); update the freeze implementation (the loop
that calls dfetch.project.create_sub_project(...),
sub_project.freeze_project(...), and the except RuntimeError as exc block) to
follow the same pattern: introduce/maintain a had_errors flag, set had_errors =
True when catching a RuntimeError, and after processing all projects raise
RuntimeError() (or rethrow) if had_errors is True so the command exits non‑zero;
if the current behavior is intentional, add a brief explanatory comment above
that try/except to document why partial failures should not fail the command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 539e8463-5a41-40ab-9401-785f53c2026c

📥 Commits

Reviewing files that changed from the base of the PR and between 04569c9 and 037049d.

📒 Files selected for processing (41)
  • CHANGELOG.rst
  • dfetch/__init__.py
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/util/util.py
  • features/check-archive.feature
  • features/check-git-repo.feature
  • features/check-specific-projects.feature
  • features/check-svn-repo.feature
  • features/checked-project-has-dependencies.feature
  • features/diff-in-git.feature
  • features/diff-in-svn.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/fetch-file-pattern-git.feature
  • features/fetch-file-pattern-svn.feature
  • features/fetch-git-repo-with-submodule.feature
  • features/fetch-git-repo.feature
  • features/fetch-single-file-git.feature
  • features/fetch-single-file-svn.feature
  • features/fetch-with-ignore-git.feature
  • features/fetch-with-ignore-svn.feature
  • features/guard-against-overwriting-git.feature
  • features/guard-against-overwriting-svn.feature
  • features/handle-invalid-metadata.feature
  • features/journey-basic-patching.feature
  • features/journey-basic-usage.feature
  • features/list-projects.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/patch-fuzzy-matching-git.feature
  • features/report-sbom.feature
  • features/suggest-project-name.feature
  • features/update-patch-in-git.feature
  • features/update-patch-in-svn.feature
  • features/updated-project-has-dependencies.feature
  • features/validate-manifest.feature
💤 Files with no reviewable changes (1)
  • dfetch/util/util.py

@spoorcc spoorcc force-pushed the claude/fix-runtime-error-printing-Vayam branch from 037049d to e59cfaa Compare April 12, 2026 12:46

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dfetch/commands/freeze.py (1)

112-140: ⚠️ Potential issue | 🟠 Major

Restore a failing exit status when any project freeze fails.

Line 135 now catches and logs per-project RuntimeErrors, but this command never records that a failure happened or raises after the loop. A run that freezes some projects and fails others will therefore exit successfully, which hides partial failures from CI/scripts and can leave a partially updated manifest reported as success.

💡 Suggested fix
 def __call__(self, args: argparse.Namespace) -> None:
     """Perform the freeze."""
     superproject = create_super_project()
     make_backup = isinstance(superproject, NoVcsSuperProject)

     manifest_updated = False
+    had_errors = False

     with in_directory(superproject.root_directory):
         manifest_path = superproject.manifest.path
         projects_to_freeze = superproject.manifest.selected_projects(args.projects)
@@
                 except RuntimeError as exc:
                     logger.print_warning_line(project.name, str(exc))
+                    had_errors = True

         if manifest_updated:
             superproject.manifest.dump()
             logger.info(f"Updated manifest ({manifest_path}) in {os.getcwd()}")
+
+    if had_errors:
+        raise RuntimeError()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/freeze.py` around lines 112 - 140, The loop currently
swallows per-project RuntimeError in the except block (except RuntimeError as
exc) without recording failure, so partial failures return success; modify the
except to set a failure flag (e.g., had_errors = True) when catching
RuntimeError (and still log the warning), then after the loop check the flag and
if True call sys.exit(1) (or raise a RuntimeError) to restore a non-zero exit
status; reference the except RuntimeError as exc block, the manifest_updated
variable/superproject.manifest.dump() post-loop, and ensure the new had_errors
flag is used to decide exit after any manifest updates are handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/settings.json:
- Around line 1-27: PR metadata and changed file contents are inconsistent: the
PR title/description mention "fix runtime error printing vayam" and
runtime/version changes, but the only changed file is .claude/settings.json (the
"permissions.allow" list). Update the PR by either (a) adding the intended code
changes related to runtime error handling/version bump (ensure relevant files
and functions are included) or (b) editing the PR title and description to
accurately describe this change to Claude permissions; confirm the actual diff
includes the expected symbols (e.g., the runtime error handling functions or
version bump commits) before marking the PR ready, and mention
.claude/settings.json in the PR body if you keep only this change.
- Line 12: The allowlist entry "Bash(pip install:*)" is too permissive; replace
it with scoped pip patterns that limit what can be installed — e.g., allow only
editable local installs ("Bash(pip install -e .:*)"), or explicit package
installs with pinned versions ("Bash(pip install some-package==1.2.3:*)"), or
installs to a restricted directory ("Bash(pip install --target=./venv
some-package==1.2.3:*)"); update the entry that currently contains "Bash(pip
install:*)" to one or more of these safer patterns and remove the wildcard form
to prevent arbitrary package execution.

---

Outside diff comments:
In `@dfetch/commands/freeze.py`:
- Around line 112-140: The loop currently swallows per-project RuntimeError in
the except block (except RuntimeError as exc) without recording failure, so
partial failures return success; modify the except to set a failure flag (e.g.,
had_errors = True) when catching RuntimeError (and still log the warning), then
after the loop check the flag and if True call sys.exit(1) (or raise a
RuntimeError) to restore a non-zero exit status; reference the except
RuntimeError as exc block, the manifest_updated
variable/superproject.manifest.dump() post-loop, and ensure the new had_errors
flag is used to decide exit after any manifest updates are handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 54ab4938-9689-4ccf-a91f-e832c8000ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 037049d and e59cfaa.

📒 Files selected for processing (44)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/__init__.py
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/util/util.py
  • features/check-archive.feature
  • features/check-git-repo.feature
  • features/check-specific-projects.feature
  • features/check-svn-repo.feature
  • features/checked-project-has-dependencies.feature
  • features/diff-in-git.feature
  • features/diff-in-svn.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/fetch-file-pattern-git.feature
  • features/fetch-file-pattern-svn.feature
  • features/fetch-git-repo-with-submodule.feature
  • features/fetch-git-repo.feature
  • features/fetch-single-file-git.feature
  • features/fetch-single-file-svn.feature
  • features/fetch-with-ignore-git.feature
  • features/fetch-with-ignore-svn.feature
  • features/guard-against-overwriting-git.feature
  • features/guard-against-overwriting-svn.feature
  • features/handle-invalid-metadata.feature
  • features/journey-basic-patching.feature
  • features/journey-basic-usage.feature
  • features/list-projects.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/patch-fuzzy-matching-git.feature
  • features/report-sbom.feature
  • features/suggest-project-name.feature
  • features/update-patch-in-git.feature
  • features/update-patch-in-svn.feature
  • features/updated-project-has-dependencies.feature
  • features/validate-manifest.feature
  • tests/test_report.py
  • tests/test_stdout_reporter.py
💤 Files with no reviewable changes (1)
  • dfetch/util/util.py

Comment thread .claude/settings.json
Comment thread .claude/settings.json Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/settings.json:
- Line 12: The entry "Bash(git update-index:*)" grants overly broad git
update-index privileges; locate the string "Bash(git update-index:*)" in
settings.json and either remove it if unused or replace the wildcard with an
explicit, minimal set of allowed invocations (e.g., enumerate the specific
flags/arguments your code actually needs) so the permission scope is constrained
and auditable; update the settings to list only those exact forms instead of
using "*" and save the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b1206ce9-e287-40a4-a8f8-39712d56d059

📥 Commits

Reviewing files that changed from the base of the PR and between e59cfaa and 30b4025.

📒 Files selected for processing (4)
  • .claude/settings.json
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/patch-after-fetch-svn.feature

Comment thread .claude/settings.json Outdated
@spoorcc spoorcc force-pushed the claude/fix-runtime-error-printing-Vayam branch from 30b4025 to 74cf207 Compare April 12, 2026 16:24
@ben-edna ben-edna force-pushed the claude/fix-runtime-error-printing-Vayam branch from 74cf207 to 9578b85 Compare April 13, 2026 06:28

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
dfetch/commands/freeze.py (1)

111-139: ⚠️ Potential issue | 🟠 Major

Preserve a failing exit status when any project cannot be frozen.

This handler only logs the RuntimeError. After this change, dfetch freeze returns success even when one or more projects failed, which hides partial failures from scripts/CI and can still leave a partially updated manifest on disk. Track a had_errors flag and raise after the loop, like the other commands in this PR.

Suggested fix
     def __call__(self, args: argparse.Namespace) -> None:
         """Perform the freeze."""
         superproject = create_super_project()
         make_backup = isinstance(superproject, NoVcsSuperProject)
 
         manifest_updated = False
+        had_errors: bool = False
 
         with in_directory(superproject.root_directory):
             manifest_path = superproject.manifest.path
             projects_to_freeze = superproject.manifest.selected_projects(args.projects)
@@
                 except RuntimeError as exc:
                     logger.print_warning_line(project.name, str(exc))
+                    had_errors = True
 
             if manifest_updated:
                 superproject.manifest.dump()
                 logger.info(f"Updated manifest ({manifest_path}) in {os.getcwd()}")
+
+        if had_errors:
+            raise RuntimeError()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/freeze.py` around lines 111 - 139, The loop over
projects_to_freeze currently catches RuntimeError and only logs it, causing
dfetch freeze to exit success even on failures; add a had_errors boolean
(initialized False) and set it True inside the except RuntimeError exc handler
(where logger.print_warning_line(project.name, str(exc)) is called), then after
the loop (after potential superproject.manifest.dump()) raise a RuntimeError or
exit non‑zero if had_errors is True to preserve failing exit status; reference
the loop that calls dfetch.project.create_sub_project,
sub_project.freeze_project, the manifest_updated logic, and
superproject.manifest.dump when adding the had_errors flag and final check.
dfetch/commands/check.py (1)

107-118: ⚠️ Potential issue | 🟠 Major

Wrap the sub-manifest scan in the same project-scoped try block.

Right now only check_for_update() is caught. If check_sub_manifests() hits an invalid nested dfetch.yaml, the RuntimeError raised by manifest parsing still escapes the loop, so later projects are skipped and this failure path never goes through had_errors. That makes dfetch check inconsistent with the new per-project error handling introduced elsewhere in this PR.

Suggested fix
             had_errors: bool = False
             for project in superproject.manifest.selected_projects(args.projects):
                 try:
                     dfetch.project.create_sub_project(project).check_for_update(
                         reporters,
                         files_to_ignore=superproject.ignored_files(project.destination),
                     )
+                    if not args.no_recommendations and os.path.isdir(project.destination):
+                        with in_directory(project.destination):
+                            check_sub_manifests(superproject.manifest, project)
                 except RuntimeError as exc:
                     logger.print_warning_line(project.name, str(exc))
                     had_errors = True
-
-                if not args.no_recommendations and os.path.isdir(project.destination):
-                    with in_directory(project.destination):
-                        check_sub_manifests(superproject.manifest, project)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/check.py` around lines 107 - 118, The call to
check_sub_manifests(superproject.manifest, project) must be executed inside the
same try/except that wraps
dfetch.project.create_sub_project(project).check_for_update(...) so that any
RuntimeError from parsing nested dfetch.yaml is caught, logged, and sets
had_errors; move the if-block (or wrap its body) into the existing try and in
the except RuntimeError as exc handler call
logger.print_warning_line(project.name, str(exc)) and set had_errors = True just
like for check_for_update to ensure consistent per-project error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/settings.json:
- Around line 8-10: The entries "Bash(git add:*)" and "Bash(git pull:*)" in
.claude/settings.json are too broad; tighten these scopes by replacing the
wildcard patterns with explicit, least-privilege command patterns or removing
them entirely—for example restrict to specific files or exact subcommands you
need (reference the exact strings "Bash(git add:*)" and "Bash(git pull:*)") or
replace with safer alternatives like only allowing "Bash(git fetch:*)" which is
already present; ensure the new entries enumerate allowed targets or exact
commands to prevent nondeterministic workspace mutations.

In `@dfetch/commands/update_patch.py`:
- Around line 90-97: The loop currently catches RuntimeError from
self._process_project(superproject, project), logs via logger.print_warning_line
and sets had_errors to continue; change this so patch failures stop immediately:
remove the try/except (or after calling logger.print_warning_line re-raise the
same RuntimeError) so that RuntimeError from _process_project (and underlying
apply_patch) propagates immediately instead of being deferred by had_errors;
leave the final had_errors block unreachable/adjust as needed. Ensure
references: _process_project, logger.print_warning_line, had_errors, and
apply_patch.

---

Outside diff comments:
In `@dfetch/commands/check.py`:
- Around line 107-118: The call to check_sub_manifests(superproject.manifest,
project) must be executed inside the same try/except that wraps
dfetch.project.create_sub_project(project).check_for_update(...) so that any
RuntimeError from parsing nested dfetch.yaml is caught, logged, and sets
had_errors; move the if-block (or wrap its body) into the existing try and in
the except RuntimeError as exc handler call
logger.print_warning_line(project.name, str(exc)) and set had_errors = True just
like for check_for_update to ensure consistent per-project error handling.

In `@dfetch/commands/freeze.py`:
- Around line 111-139: The loop over projects_to_freeze currently catches
RuntimeError and only logs it, causing dfetch freeze to exit success even on
failures; add a had_errors boolean (initialized False) and set it True inside
the except RuntimeError exc handler (where
logger.print_warning_line(project.name, str(exc)) is called), then after the
loop (after potential superproject.manifest.dump()) raise a RuntimeError or exit
non‑zero if had_errors is True to preserve failing exit status; reference the
loop that calls dfetch.project.create_sub_project, sub_project.freeze_project,
the manifest_updated logic, and superproject.manifest.dump when adding the
had_errors flag and final check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d6c1aca-37b4-41c5-abf9-fc46b333f2ba

📥 Commits

Reviewing files that changed from the base of the PR and between 30b4025 and 9578b85.

📒 Files selected for processing (44)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/__init__.py
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/util/util.py
  • features/check-archive.feature
  • features/check-git-repo.feature
  • features/check-specific-projects.feature
  • features/check-svn-repo.feature
  • features/checked-project-has-dependencies.feature
  • features/diff-in-git.feature
  • features/diff-in-svn.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/fetch-file-pattern-git.feature
  • features/fetch-file-pattern-svn.feature
  • features/fetch-git-repo-with-submodule.feature
  • features/fetch-git-repo.feature
  • features/fetch-single-file-git.feature
  • features/fetch-single-file-svn.feature
  • features/fetch-with-ignore-git.feature
  • features/fetch-with-ignore-svn.feature
  • features/guard-against-overwriting-git.feature
  • features/guard-against-overwriting-svn.feature
  • features/handle-invalid-metadata.feature
  • features/journey-basic-patching.feature
  • features/journey-basic-usage.feature
  • features/list-projects.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/patch-fuzzy-matching-git.feature
  • features/report-sbom.feature
  • features/suggest-project-name.feature
  • features/update-patch-in-git.feature
  • features/update-patch-in-svn.feature
  • features/updated-project-has-dependencies.feature
  • features/validate-manifest.feature
  • tests/test_report.py
  • tests/test_stdout_reporter.py
💤 Files with no reviewable changes (1)
  • dfetch/util/util.py

Comment thread .claude/settings.json Outdated
Comment on lines +90 to +97
try:
self._process_project(superproject, project)
except RuntimeError as exc:
logger.print_warning_line(project.name, str(exc))
had_errors = True

if had_errors:
raise RuntimeError()

@coderabbitai coderabbitai Bot Apr 13, 2026

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.

⚠️ Potential issue | 🟠 Major

Do not continue to the next project after a patch-update failure.

_process_project() does two forced subproject.update() calls and may overwrite the patch file before the final re-apply. Catching RuntimeError here and then moving on to other projects means a failed patch operation can leave the repo in an intermediate state while the command keeps mutating more projects. For this command, log the project-scoped warning and re-raise immediately instead of deferring failure until the end.

Suggested fix
         with in_directory(superproject.root_directory):
             for project in superproject.manifest.selected_projects(args.projects):
                 try:
                     self._process_project(superproject, project)
                 except RuntimeError as exc:
                     logger.print_warning_line(project.name, str(exc))
-                    had_errors = True
-
-        if had_errors:
-            raise RuntimeError()
+                    raise

Based on learnings, "patch application failures should fail immediately by letting RuntimeError propagate from apply_patch() rather than catching and continuing with remaining patches. The project prefers explicit failures for patch operations."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/update_patch.py` around lines 90 - 97, The loop currently
catches RuntimeError from self._process_project(superproject, project), logs via
logger.print_warning_line and sets had_errors to continue; change this so patch
failures stop immediately: remove the try/except (or after calling
logger.print_warning_line re-raise the same RuntimeError) so that RuntimeError
from _process_project (and underlying apply_patch) propagates immediately
instead of being deferred by had_errors; leave the final had_errors block
unreachable/adjust as needed. Ensure references: _process_project,
logger.print_warning_line, had_errors, and apply_patch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

patch application failures should fail immediately within a subproject, patch application can continue for other subprojects

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.

@spoorcc, understood — the failure should be immediate within a subproject (i.e., _process_project exits on its first RuntimeError without attempting further steps for that same project), but the outer loop correctly continues on to the remaining subprojects. The current implementation already does exactly that. My suggestion was wrong. I'll update the learning to reflect the correct scope.


✏️ Learnings added
Learnt from: spoorcc
Repo: dfetch-org/dfetch PR: 1129
File: dfetch/commands/update_patch.py:90-97
Timestamp: 2026-04-13T17:26:12.856Z
Learning: In dfetch's patch-related commands (e.g., dfetch/commands/update_patch.py), a RuntimeError during processing of a single subproject should cause that subproject's processing to stop immediately (fail fast within the subproject), but the outer loop should continue processing remaining subprojects. After the loop, a bare RuntimeError() is raised if any project failed. This is the intentional design: per-subproject failure isolation, not abort-all-on-first-error.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


⛔ Learnings removed
Learnt from: spoorcc
Repo: dfetch-org/dfetch PR: 942
File: dfetch/project/subproject.py:143-166
Timestamp: 2026-01-09T23:16:35.480Z
Learning: In dfetch project's dfetch/project/subproject.py, patch application failures should fail immediately by letting RuntimeError propagate from apply_patch() rather than catching and continuing with remaining patches. The project prefers explicit failures for patch operations.

🧠 Learnings used
Learnt from: spoorcc
Repo: dfetch-org/dfetch PR: 944
File: dfetch/commands/update_patch.py:124-129
Timestamp: 2026-01-30T22:08:36.677Z
Learning: In dfetch's update-patch command (dfetch/commands/update_patch.py), when generating a reverse diff for a subproject patch, the old_revision parameter should be superproject.current_revision() (not subproject.current_revision()) because the patch is computed against the superproject's VCS commit to capture local changes within the superproject's version control context.

Learnt from: spoorcc
Repo: dfetch-org/dfetch PR: 942
File: dfetch/project/subproject.py:143-166
Timestamp: 2026-01-09T23:16:35.480Z
Learning: In dfetch project's dfetch/project/subproject.py, patch application failures should fail immediately by letting RuntimeError propagate from apply_patch() rather than catching and continuing with remaining patches. The project prefers explicit failures for patch operations.

Learnt from: ben-edna
Repo: dfetch-org/dfetch PR: 898
File: script/package.py:201-205
Timestamp: 2026-01-01T11:05:43.761Z
Learning: In the dfetch project's script/package.py, prefer explicit failures ("crash and burn") over silently handling missing configuration values, ensuring that builds fail if required metadata (e.g., URLs in pyproject.toml) is not properly configured.

@ben-edna ben-edna linked an issue Apr 13, 2026 that may be closed by this pull request
@ben-edna ben-edna changed the title Claude/fix runtime error printing vayam fix runtime error printing Apr 13, 2026
@spoorcc

spoorcc commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

@spoorcc Sure! I'll resume reviews for this PR and kick off a review now.

✅ Actions performed

Reviews resumed.


Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dfetch/commands/freeze.py (1)

97-145: 🛠️ Refactor suggestion | 🟠 Major

Cyclomatic complexity exceeds the threshold of 8.

The __call__ method now has approximately 10 branches (base + with + if make_backup + for + try/except + if/elif/else for new_version + if manifest_updated + if had_errors). As per coding guidelines, cyclomatic complexity must stay below 8 per function.

Consider extracting the version-detection and logging logic (lines 118-135) into a helper method:

♻️ Proposed refactor to reduce complexity
+    def _log_freeze_result(
+        self, project: dfetch.manifest.project.ProjectEntry, 
+        on_disk_version: str | None, 
+        new_version: str | None
+    ) -> None:
+        """Log the result of freezing a project."""
+        if new_version is None:
+            if on_disk_version:
+                logger.print_info_line(
+                    project.name,
+                    f"Already pinned in manifest on version {on_disk_version}",
+                )
+            else:
+                logger.print_warning_line(
+                    project.name,
+                    "No version on disk, first update with 'dfetch update'",
+                )
+        else:
+            logger.print_info_line(
+                project.name,
+                f"Frozen on version {new_version}",
+            )
+
     def __call__(self, args: argparse.Namespace) -> None:
         """Perform the freeze."""
         # ... existing setup code ...
         
         for project in projects_to_freeze:
             try:
                 sub_project = dfetch.project.create_sub_project(project)
                 on_disk_version = sub_project.on_disk_version()
                 new_version = sub_project.freeze_project(project)
-                if new_version is None:
-                    if on_disk_version:
-                        logger.print_info_line(
-                            project.name,
-                            f"Already pinned in manifest on version {on_disk_version}",
-                        )
-                    else:
-                        logger.print_warning_line(
-                            project.name,
-                            "No version on disk, first update with 'dfetch update'",
-                        )
-                else:
-                    logger.print_info_line(
-                        project.name,
-                        f"Frozen on version {new_version}",
-                    )
+                self._log_freeze_result(project, on_disk_version, new_version)
+                if new_version:
                     superproject.manifest.update_project_version(project)
                     manifest_updated = True
             except RuntimeError as exc:
                 # ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/freeze.py` around lines 97 - 145, The __call__ method in
freeze.py is over-complex; extract the per-project version detection and logging
(the logic that calls dfetch.project.create_sub_project,
sub_project.on_disk_version(), sub_project.freeze_project(project), and the
subsequent logger.print_* calls and
superproject.manifest.update_project_version(project) behavior) into a new
helper function (e.g., _process_and_freeze_project(superproject, project) or
similar). Move the try/except into that helper so the loop in __call__ becomes a
simple iterate-and-call, have the helper return (manifest_was_updated: bool,
had_error: bool) or raise as appropriate, and update manifest_updated and
had_errors in __call__ accordingly; keep create_super_project(),
NoVcsSuperProject backup handling, in_directory context, and final
manifest.dump()/logger.info and had_errors check in __call__ unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/settings.json:
- Around line 21-22: The current settings.json uses broad command patterns
"Bash(radon *)" and "Bash(ruff check:*)" which run across the whole workspace;
restrict these patterns to the specific project paths to avoid scanning
unrelated repos. Update the two entries to include the repository-specific
path(s) or glob(s) (e.g., "Bash(radon <project_path> *)" and "Bash(ruff
check:<project_path> *)" or similar per your project layout) so the radon and
ruff commands only target the intended codebase; keep the exact string forms
"Bash(radon *)" and "Bash(ruff check:*)" as the identifiers to locate and
replace them in .claude/settings.json.
- Line 15: The entry "Bash(pre-commit run:*)" is too permissive — replace the
wildcard pattern with explicit allowed commands/hook names so only known
pre-commit hooks can execute (e.g., enumerate exact hook names instead of using
:*); do the same for the other permissive entries that use the :* wildcard (the
similar Bash(...) entries on the file), and ensure the allowed set matches
concrete script names in your pre-commit config to tighten execution scope and
improve auditability.

---

Outside diff comments:
In `@dfetch/commands/freeze.py`:
- Around line 97-145: The __call__ method in freeze.py is over-complex; extract
the per-project version detection and logging (the logic that calls
dfetch.project.create_sub_project, sub_project.on_disk_version(),
sub_project.freeze_project(project), and the subsequent logger.print_* calls and
superproject.manifest.update_project_version(project) behavior) into a new
helper function (e.g., _process_and_freeze_project(superproject, project) or
similar). Move the try/except into that helper so the loop in __call__ becomes a
simple iterate-and-call, have the helper return (manifest_was_updated: bool,
had_error: bool) or raise as appropriate, and update manifest_updated and
had_errors in __call__ accordingly; keep create_super_project(),
NoVcsSuperProject backup handling, in_directory context, and final
manifest.dump()/logger.info and had_errors check in __call__ unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a242edc4-ab2e-4a33-ab5a-e49766aec8fa

📥 Commits

Reviewing files that changed from the base of the PR and between 9578b85 and dce1821.

📒 Files selected for processing (10)
  • .claude/settings.json
  • dfetch/commands/check.py
  • dfetch/commands/freeze.py
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/updated-project-has-dependencies.feature
💤 Files with no reviewable changes (1)
  • features/fetch-checks-destination.feature

Comment thread .claude/settings.json
Comment thread .claude/settings.json
@ben-edna ben-edna force-pushed the claude/fix-runtime-error-printing-Vayam branch from dce1821 to a6d4836 Compare June 12, 2026 15:26

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/settings.json:
- Line 8: The entry "Bash(git fetch:*)" grants unrestricted git fetch args;
narrow it to the exact fetch invocations the workflow requires by replacing
"Bash(git fetch:*)" with explicit, least-privilege fetch forms (e.g., fetch from
the specific remote names and exact refspecs or flags your workflow uses) so
only those exact git fetch commands are allowed; update the settings.json entry
accordingly and remove the wildcard form.

In `@dfetch/util/util.py`:
- Around line 360-371: The function glob_within_root misclassifies matches
because it compares Path(p).resolve() to the caller-provided root without
resolving root first; change the implementation in glob_within_root to resolve
the caller-provided root once (e.g., root_resolved = root.resolve()) before the
loop and then use Path(p).resolve().is_relative_to(root_resolved) to classify
into safe and escaped (keep using the same safe/escaped lists and the existing
sorted(glob.glob(pattern)) iteration).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b1226d8e-4d56-47e0-b0c9-c604806cb34f

📥 Commits

Reviewing files that changed from the base of the PR and between dce1821 and a6d4836.

📒 Files selected for processing (16)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/util/util.py
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/updated-project-has-dependencies.feature

@coderabbitai coderabbitai Bot 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/settings.json:
- Line 8: The entry "Bash(git fetch:*)" grants unrestricted git fetch args;
narrow it to the exact fetch invocations the workflow requires by replacing
"Bash(git fetch:*)" with explicit, least-privilege fetch forms (e.g., fetch from
the specific remote names and exact refspecs or flags your workflow uses) so
only those exact git fetch commands are allowed; update the settings.json entry
accordingly and remove the wildcard form.

In `@dfetch/util/util.py`:
- Around line 360-371: The function glob_within_root misclassifies matches
because it compares Path(p).resolve() to the caller-provided root without
resolving root first; change the implementation in glob_within_root to resolve
the caller-provided root once (e.g., root_resolved = root.resolve()) before the
loop and then use Path(p).resolve().is_relative_to(root_resolved) to classify
into safe and escaped (keep using the same safe/escaped lists and the existing
sorted(glob.glob(pattern)) iteration).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b1226d8e-4d56-47e0-b0c9-c604806cb34f

📥 Commits

Reviewing files that changed from the base of the PR and between dce1821 and a6d4836.

📒 Files selected for processing (16)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/util/util.py
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/updated-project-has-dependencies.feature
🛑 Comments failed to post (2)
.claude/settings.json (1)

8-8: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope git fetch more tightly.

Line 8 allows arbitrary git fetch arguments, which gives the agent unrestricted remote selection, refspec updates, and network egress in a security-sensitive allowlist. Replace the wildcard with the exact fetch forms the workflow needs so the permission stays auditable and least-privilege.

🔒 Suggested hardening
-      "Bash(git fetch:*)",
+      "Bash(git fetch origin)",
+      "Bash(git fetch origin --tags)",
+      "Bash(git fetch --all --prune)"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      "Bash(git fetch origin)",
      "Bash(git fetch origin --tags)",
      "Bash(git fetch --all --prune)"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/settings.json at line 8, The entry "Bash(git fetch:*)" grants
unrestricted git fetch args; narrow it to the exact fetch invocations the
workflow requires by replacing "Bash(git fetch:*)" with explicit,
least-privilege fetch forms (e.g., fetch from the specific remote names and
exact refspecs or flags your workflow uses) so only those exact git fetch
commands are allowed; update the settings.json entry accordingly and remove the
wildcard form.
dfetch/util/util.py (1)

360-371: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve root before classifying glob matches.

Line 370 compares Path(p).resolve() against the caller-provided root as-is. If root is relative or still contains symlinks / .., safe matches can be misclassified as escaped.

Suggested fix
 def glob_within_root(pattern: str, root: Path) -> tuple[list[str], list[str]]:
     """Expand *pattern* and split results by whether they resolve within *root*.
@@
     """
     safe: list[str] = []
     escaped: list[str] = []
+    resolved_root = root.resolve()
     for p in sorted(glob.glob(pattern)):
-        (safe if Path(p).resolve().is_relative_to(root) else escaped).append(p)
+        resolved_path = Path(p).resolve()
+        (safe if resolved_path.is_relative_to(resolved_root) else escaped).append(p)
     return safe, escaped
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

def glob_within_root(pattern: str, root: Path) -> tuple[list[str], list[str]]:
    """Expand *pattern* and split results by whether they resolve within *root*.

    Returns:
        A ``(safe, escaped)`` tuple where *safe* contains sorted paths that
        resolve inside *root* and *escaped* contains those that do not.
    """
    safe: list[str] = []
    escaped: list[str] = []
    resolved_root = root.resolve()
    for p in sorted(glob.glob(pattern)):
        resolved_path = Path(p).resolve()
        (safe if resolved_path.is_relative_to(resolved_root) else escaped).append(p)
    return safe, escaped
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/util/util.py` around lines 360 - 371, The function glob_within_root
misclassifies matches because it compares Path(p).resolve() to the
caller-provided root without resolving root first; change the implementation in
glob_within_root to resolve the caller-provided root once (e.g., root_resolved =
root.resolve()) before the loop and then use
Path(p).resolve().is_relative_to(root_resolved) to classify into safe and
escaped (keep using the same safe/escaped lists and the existing
sorted(glob.glob(pattern)) iteration).

@spoorcc spoorcc force-pushed the claude/fix-runtime-error-printing-Vayam branch from e806cdd to 70412c7 Compare June 14, 2026 15:01
@spoorcc spoorcc force-pushed the claude/fix-runtime-error-printing-Vayam branch from 70412c7 to f676382 Compare June 14, 2026 15:29
@spoorcc

spoorcc commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@spoorcc

spoorcc commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@spoorcc

spoorcc commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
.claude/settings.json (1)

15-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-scoping broad Bash(...) wildcard permissions is needed.

Line 15, Lines 20–24, Lines 27–28, and Lines 32–35 reintroduce very broad execution permissions (e.g., *-based patterns) for commands that can execute project code or mutate the environment. This weakens least-privilege and auditability in the same way previously flagged patterns did.

Suggested hardening examples
- "Bash(python -m behave *)",
+ "Bash(python -m behave features:*)",

- "Bash(python -m pytest tests/*)",
+ "Bash(pytest tests/*)",

- "Bash(radon *)",
+ "Bash(radon dfetch:*)",

- "Bash(ruff check:*)",
+ "Bash(ruff check dfetch:*)",

- "Bash(xenon *)",
+ "Bash(xenon dfetch:*)",

- "Bash(pip install *)",
+ "Bash(pip install -e .)"

Also applies to: 20-24, 27-28, 32-35

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/settings.json at line 15, The file .claude/settings.json contains
overly broad wildcard patterns in Bash permission rules that weaken
least-privilege security. At line 15, replace the broad pattern "Bash(pre-commit
run:*)" with a specific list of allowed pre-commit hooks instead of using the
wildcard. At lines 20-24, 27-28, and 32-35, apply the same principle by removing
wildcard patterns and replacing them with explicit, narrow permission scopes
that specify exactly which commands or command variations are permitted rather
than allowing any command matching a broad pattern. Ensure each Bash entry
specifies only the minimum necessary execution permissions needed for intended
functionality.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_fuzzing.py`:
- Around line 56-65: The SAFE_VERSION strategy in test_fuzzing.py no longer
includes string generation, which means the Str() branch of the MANIFEST_SCHEMA
version validator is not being tested during fuzzing. Add string generation to
the SAFE_VERSION st.one_of() call to ensure all three branches (Int(), Float(),
and Str()) are covered by the fuzzed test paths (test_manifest_can_be_created,
test_check, test_update), while maintaining the safety constraints already
documented in the comments about avoiding non-float strings that would cause
strictyaml to raise ValueError.

---

Duplicate comments:
In @.claude/settings.json:
- Line 15: The file .claude/settings.json contains overly broad wildcard
patterns in Bash permission rules that weaken least-privilege security. At line
15, replace the broad pattern "Bash(pre-commit run:*)" with a specific list of
allowed pre-commit hooks instead of using the wildcard. At lines 20-24, 27-28,
and 32-35, apply the same principle by removing wildcard patterns and replacing
them with explicit, narrow permission scopes that specify exactly which commands
or command variations are permitted rather than allowing any command matching a
broad pattern. Ensure each Bash entry specifies only the minimum necessary
execution permissions needed for intended functionality.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48ef632a-b1a3-46b5-a033-fe131a4af4d7

📥 Commits

Reviewing files that changed from the base of the PR and between a6d4836 and f676382.

📒 Files selected for processing (21)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/commands/check.py
  • dfetch/commands/diff.py
  • dfetch/commands/environment.py
  • dfetch/commands/format_patch.py
  • dfetch/commands/freeze.py
  • dfetch/commands/update.py
  • dfetch/commands/update_patch.py
  • dfetch/log.py
  • dfetch/project/svnsubproject.py
  • dfetch/util/util.py
  • dfetch/vcs/svn.py
  • features/check-git-repo.feature
  • features/check-svn-repo.feature
  • features/fetch-archive.feature
  • features/fetch-checks-destination.feature
  • features/patch-after-fetch-git.feature
  • features/patch-after-fetch-svn.feature
  • features/updated-project-has-dependencies.feature
  • tests/test_fuzzing.py
💤 Files with no reviewable changes (1)
  • features/fetch-checks-destination.feature

Comment thread tests/test_fuzzing.py Outdated

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
doc/_ext/scenario_directive.py (1)

497-514: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Node reuse causes only the last placeholder to receive content when multiple exist.

If multiple .. scenario-appendix:: directives are present, the same note node (HTML) or appendix_nodes list (PDF) is passed to replace_self for each placeholder. In docutils, inserting a node into a new parent removes it from the old one, so only the last placeholder retains content.

While documentation says to use the directive once, the code should handle duplicates gracefully.

Proposed fix: create fresh note per placeholder in HTML case
     if app.builder.name not in ("latex", "rinoh"):
+        for placeholder in placeholders:
+            note = nodes.note()
+            para = nodes.paragraph()
+            para += nodes.Text(
+                "In the HTML edition, feature examples appear as expandable "
+                "blocks directly within each guide section. "
+                "In the PDF edition they are collected here, grouped by command."
+            )
+            note += para
+            placeholder.replace_self([note])
+        return
-        note = nodes.note()
-        para = nodes.paragraph()
-        para += nodes.Text(
-            "In the HTML edition, feature examples appear as expandable "
-            "blocks directly within each guide section. "
-            "In the PDF edition they are collected here, grouped by command."
-        )
-        note += para
-        for placeholder in placeholders:
-            placeholder.replace_self([note])
-        return

     entries = getattr(app.env, "scenario_appendix_entries", {})
     appendix_nodes = _build_appendix_nodes(entries) if entries else []

-    for placeholder in placeholders:
-        placeholder.replace_self(appendix_nodes)
+    for i, placeholder in enumerate(placeholders):
+        if i == 0:
+            placeholder.replace_self(appendix_nodes)
+        else:
+            # Remove duplicate appendix placeholders; content appears once
+            placeholder.replace_self([])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/_ext/scenario_directive.py` around lines 497 - 514, The issue is that the
same node object (note in HTML case, appendix_nodes in PDF case) is being reused
across multiple placeholders in the loop. In docutils, nodes can only have one
parent, so when the same node is inserted into multiple parents, it gets removed
from previous parents. To fix this, for the HTML case (when app.builder.name is
not "latex" or "rinoh"), move the note node creation inside the for loop
iterating over placeholders so each placeholder receives its own fresh note
object. Similarly, for the PDF case, either move the _build_appendix_nodes call
inside the loop for each placeholder, or create independent copies of the
appendix_nodes for each placeholder instead of reusing the same list across all
iterations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@doc/_ext/scenario_directive.py`:
- Around line 497-514: The issue is that the same node object (note in HTML
case, appendix_nodes in PDF case) is being reused across multiple placeholders
in the loop. In docutils, nodes can only have one parent, so when the same node
is inserted into multiple parents, it gets removed from previous parents. To fix
this, for the HTML case (when app.builder.name is not "latex" or "rinoh"), move
the note node creation inside the for loop iterating over placeholders so each
placeholder receives its own fresh note object. Similarly, for the PDF case,
either move the _build_appendix_nodes call inside the loop for each placeholder,
or create independent copies of the appendix_nodes for each placeholder instead
of reusing the same list across all iterations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7097eaa8-1626-432f-bcb0-35a7da59be4c

📥 Commits

Reviewing files that changed from the base of the PR and between f676382 and 716ec0d.

📒 Files selected for processing (3)
  • doc/_ext/colordot.py
  • doc/_ext/scenario_directive.py
  • tests/test_fuzzing.py

spoorcc and others added 15 commits June 14, 2026 17:46
Replace the pattern of collecting RuntimeErrors across all subprojects
and printing them at the end with immediate in-context logging via
`logger.print_warning_line(project.name, str(exc))` at the point of
failure. This ensures errors like "svn not available on system" appear
next to the subproject they belong to instead of at the end of the run.

The final `raise RuntimeError()` (no message) still produces a non-zero
exit code without double-printing.

Also bumps version to 0.14.0 and updates CHANGELOG, feature tests.

https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract per-project logic from Diff.__call__ (was CC=11) into
_diff_project and from UpdatePatch.__call__ (was CC=10) into
_process_project. All methods now have CC <= 8.

https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the pattern of collecting RuntimeErrors across all subprojects
and printing them at the end with immediate in-context logging via
`logger.print_warning_line(project.name, str(exc))` at the point of
failure. This ensures errors like "svn not available on system" appear
next to the subproject they belong to instead of at the end of the run.

The final `raise RuntimeError()` (no message) still produces a non-zero
exit code without double-printing.

Also bumps version to 0.14.0 and updates CHANGELOG, feature tests.

https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolve root once before the loop in glob_within_root so symlinked or
relative root paths compare correctly against resolved match paths.

Narrow the Claude Code permission from the wildcard "git fetch:*" to
"git fetch origin:*", restricting fetches to the origin remote only.

https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu
Docutils nodes can only have one parent. Creating a single note (HTML)
or a single appendix-nodes list (PDF) and inserting it into multiple
placeholders caused earlier placeholders to silently lose their node
once it was re-parented. Move node creation inside the placeholder loop
so each replacement receives its own fresh node tree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@spoorcc spoorcc force-pushed the claude/fix-runtime-error-printing-Vayam branch from dc0e595 to c80559e Compare June 14, 2026 17:46
@spoorcc spoorcc merged commit 54708e5 into main Jun 14, 2026
36 checks passed
@spoorcc spoorcc deleted the claude/fix-runtime-error-printing-Vayam branch June 14, 2026 18:16
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.

svn missing message out-of-context

3 participants