Skip to content

Add error handling to diagnostic build process#8

Open
GautamKumarOffical wants to merge 6 commits into
thanhle74:mainfrom
GautamKumarOffical:feat/build-error-handling
Open

Add error handling to diagnostic build process#8
GautamKumarOffical wants to merge 6 commits into
thanhle74:mainfrom
GautamKumarOffical:feat/build-error-handling

Conversation

@GautamKumarOffical

@GautamKumarOffical GautamKumarOffical commented Jun 19, 2026

Copy link
Copy Markdown

Summary

This PR adds comprehensive error handling to the diagnostic build process in build.py as requested in issue #2.

Changes

  • Added error handling for file I/O operations in split_diagnostic_logd()
  • Improved collect_system_info() with graceful failure modes for each system call
  • Enhanced generate_logd() to handle timeouts, file not found, and OS errors
  • Added error handling for build_module() with better error messages
  • Improved clean_module() with specific exception types
  • Added top-level exception handler in main() for keyboard interrupts and fatal errors
  • Added module directory existence check in build_module()

Acceptance Criteria

  • ✅ Error handling implemented across all diagnostic functions
  • ✅ Graceful failure modes - the script continues running when individual components fail
  • ✅ Diagnostic logging preserved - JSON reports are always written even when errors occur
  • ✅ Code follows existing conventions

Testing

Ran python3 build.py -m nfc-scanner to verify the build process works correctly with the error handling changes. The diagnostic report was successfully generated.

Summary by CodeRabbit

  • Bug Fixes
    • Improved build reliability: safer handling for missing module directories, timeouts, missing commands, OS-level errors, and unexpected failures.
    • Hardened diagnostic handling: more resilient diagnostic log splitting and best-effort error recovery; guarded reporting writes.
    • Improved diagnostics/system info collection: failures now surface as “unavailable” and error metadata is generated more consistently.
    • More robust CLI behavior with correct exit codes on argument parsing issues and fatal interrupts.
  • Chores
    • Standardized deterministic UTF-8 console/subprocess text handling for more readable build logs.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ebb8ba4-3746-4057-903d-c87ec981fc33

📥 Commits

Reviewing files that changed from the base of the PR and between f6cf2ee and 7c15568.

📒 Files selected for processing (5)
  • build.py
  • diagnostic/build-396da43c.json
  • diagnostic/build-396da43c.logd
  • diagnostic/build-c2454c25.json
  • diagnostic/build-c2454c25.logd
🚧 Files skipped from review as they are similar to previous changes (1)
  • diagnostic/build-c2454c25.json

📝 Walkthrough

Walkthrough

build.py receives comprehensive defensive error handling across all major execution paths: UTF-8 subprocess text handling, diagnostic log chunking, module build/clean operations, system information collection, diagnostic bundle generation, and the main entrypoint. Each area gains try/except blocks covering OSError, FileNotFoundError, TimeoutExpired, and catch-all handlers, returning structured failure values instead of raising. New diagnostic report artifacts demonstrate the updated system capturing build results, module outputs, and encrypted diagnostic metadata for sample builds.

Changes

build.py Error Handling Hardening

Layer / File(s) Summary
UTF-8 subprocess text handling foundation
build.py
Introduces TEXT_ENCODING constant, configure_text_encoding(), subprocess_env(), and run_text_process() functions to ensure deterministic UTF-8 decoding for all subprocess text operations. Updates current_commit_id() to use the new wrapper.
Diagnostic log chunking resilience
build.py
Rewrites split_diagnostic_logd() to check for missing input logs, guard file-size checks, wrap chunk writing in OSError handlers with cleanup of already-written chunks on failure, and return best-effort paths instead of raising.
Module build and clean execution resilience
build.py
build_module() now prints a build header, fails early if module.dir is missing, wraps env.update() to catch env-setting errors, and adds explicit FileNotFoundError/OSError handlers for npm install, CMake configure, and the main build subprocess with consistent failure tuples. clean_module() adds missing-directory early exit and expands exception handling to cover TimeoutExpired, FileNotFoundError, OSError, and catch-all exceptions with per-error messages.
System information collection and subprocess utility hardening
build.py
collect_system_info() wraps each field (timestamp, hostname, user, platform, processor, cpu count) in individual try/except blocks emitting "unavailable" on error, and similarly guards environment-variable reads. run_cmd() routes subprocess execution through run_text_process() for consistent UTF-8 decoding. commit_diagnostic_artifacts() updates subprocess calls to use run_text_process(). check_encryptly_runs() adds structured failure handling returning (False, message) tuples.
Diagnostic bundle generation resilience
build.py
generate_logd() wraps initial diagnostic JSON metadata write with early False return on OSError. Guards system-info.txt and build-summary.txt file writes independently with OSError handlers. Wraps build.log write and encryptly pack invocation with TimeoutExpired, FileNotFoundError, and OSError handlers that write error reports and clean up artifacts. Validates that log splitting produces chunks (fails if none). Wraps final diagnostic report writing and tolerates per-chunk stat failures. Adds a final catch-all unexpected-error handler that always cleans the workspace in finally.
Main and main entrypoint control flow
build.py
parse_args() exceptions return exit code 1. The --clean and build loops catch per-module unexpected exceptions and continue iteration. Artifact removal is guarded with OSError handling per artifact. The __main__ entrypoint handles KeyboardInterrupt (exit 130) and fatal exceptions (exit 1) with printed messages.
Supporting docstrings and helper documentation
build.py
Adds docstrings to platform/OS/architecture helpers, encryptly platform selection, Colors/color(), check_prerequisites(), build_diagnostic_report(), and write_diagnostic_report() without changing logic.

Build Diagnostic Report Artifacts

Layer / File(s) Summary
Build diagnostic results for commit c2454c2
diagnostic/build-c2454c25.json
Records build results including generation timestamp, module pass/fail counts (9 passed, 1 failed), per-module status and captured build output/error logs including Rust compiler warnings and GHC errors, encrypted diagnostic log artifact reference with password and decryption command, and a maintenance note instructing inclusion of the encrypted .logd artifact for PR review.
Build diagnostic results for commit 396da43
diagnostic/build-396da43c.json
Records build results including generation timestamp, module pass/fail counts (9 passed, 1 failed), per-module status and captured output with multiple successful builds and one openapi-haskell GHC failure showing missing Haskell module dependencies, encrypted diagnostic log artifact reference with password and decryption command, and a maintenance note instructing inclusion of the encrypted .logd artifact for PR review.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hoppity-hop through the error-prone maze,
Each except a patch in the try/except haze.
OSError? No crash — just an "unavailable" note,
Pack timeout? Clean up, and write the error quote.
The build loop keeps going, module by module it runs,
No fatal crash stops this bunny — we always have fun! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add error handling to diagnostic build process' accurately summarizes the main change: comprehensive error handling added to build.py's diagnostic functions.
Description check ✅ Passed The PR description follows the required template with all sections completed: Summary, Changes, Testing details, and a comprehensive Acceptance Criteria checklist showing completion.
Docstring Coverage ✅ Passed Docstring coverage is 95.83% 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

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

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
build.py (1)

735-736: 💤 Low value

Unused variable decrypt_target.

The decrypt_target computed on line 736 is never used. The build_diagnostic_report function computes its own decrypt_target internally based on logd_relpaths.

Proposed fix
         logd_relpaths = [str(path.relative_to(ROOT)) for path in logd_files]
-        decrypt_target = logd_relpaths[0] if len(logd_relpaths) == 1 else str(logd_path.relative_to(ROOT))
         try:
             write_diagnostic_report(
🤖 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 `@build.py` around lines 735 - 736, The variable `decrypt_target` is computed
on line 736 but is never used anywhere in the code since the
`build_diagnostic_report` function computes its own `decrypt_target` internally
based on `logd_relpaths`. Remove the unused `decrypt_target` variable assignment
that selects between logd_relpaths[0] and str(logd_path.relative_to(ROOT)),
keeping only the `logd_relpaths` assignment which is actually needed by the
downstream function.
🤖 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 `@build.py`:
- Around line 82-86: The issue is that when logd_path.unlink() fails in the
except OSError block after chunks have been successfully created and written to
disk, the handler returns the original logd_path instead of handling the
already-created chunks, leaving orphaned chunk files on disk. Modify the except
OSError block to either return the chunks list that was successfully created
(since they're complete and valid) or clean up those chunk files before
returning the original file, depending on your preferred strategy for handling
this failure case.

---

Nitpick comments:
In `@build.py`:
- Around line 735-736: The variable `decrypt_target` is computed on line 736 but
is never used anywhere in the code since the `build_diagnostic_report` function
computes its own `decrypt_target` internally based on `logd_relpaths`. Remove
the unused `decrypt_target` variable assignment that selects between
logd_relpaths[0] and str(logd_path.relative_to(ROOT)), keeping only the
`logd_relpaths` assignment which is actually needed by the downstream function.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f973fd9-79db-442f-ae21-e65c051ab68b

📥 Commits

Reviewing files that changed from the base of the PR and between e5de93b and f7e9fe2.

📒 Files selected for processing (1)
  • build.py

Comment thread build.py Outdated
@GautamKumarOffical

Copy link
Copy Markdown
Author

Fixed the review comments:

  1. build.py line 82 — logd_path.unlink() is now wrapped in try/except OSError so if it fails after chunks are written, the chunks are preserved and a warning is printed instead of silently returning the original path.

Note: the decrypt_target variable on line 736 IS used on line 775 in the print statement for the encryptly unpack command, so it's not actually unused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (5)
build.py (5)

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

Return no artifacts when the source log is missing.

generate_logd() treats any non-empty result from split_diagnostic_logd() as success. Returning [logd_path] here can write final metadata that references a .logd file that does not exist.

Proposed fix
         if not logd_path.exists():
             print(f"    {color('✗', Colors.RED)} Cannot split: {logd_path.name} does not exist")
-            return [logd_path]
+            return []
🤖 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 `@build.py` around lines 52 - 54, In the split_diagnostic_logd() function, when
the source log file does not exist (when not logd_path.exists()), the function
should return an empty list instead of returning [logd_path]. This prevents
generate_logd() from treating this as a successful result and writing metadata
that references a non-existent .logd file. Change the return statement to return
an empty list to properly indicate that no artifacts were produced.

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

Bound diagnostic subprocesses with a default timeout.

collect_system_info() reaches run_cmd() for commands like df -h; without a timeout, diagnostic generation can still hang indefinitely on unhealthy mounts or stalled commands.

Proposed fix
 def run_cmd(cmd: list[str], **kwargs) -> tuple[bool, str]:
     """Run a command and return success status and combined output."""
     try:
+        kwargs.setdefault("timeout", 15)
         result = subprocess.run(
             cmd, capture_output=True, text=True, check=False, **kwargs
         )
         output = result.stdout
         if result.stderr:
             output += "\n" + result.stderr
         return result.returncode == 0, output.strip()
+    except subprocess.TimeoutExpired as e:
+        return False, f"timeout after {e.timeout}s"
     except Exception as e:
         return False, str(e)
🤖 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 `@build.py` around lines 444 - 455, The run_cmd function does not have a
timeout parameter when calling subprocess.run(), which allows commands to hang
indefinitely on unhealthy mounts or stalled processes. Add a timeout parameter
(with a sensible default value like 30 seconds) to the subprocess.run() call,
and extend the exception handling block to catch subprocess.TimeoutExpired
exceptions separately. When a timeout occurs, return False with an appropriate
error message indicating the command timed out.

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

Keep cleanup failures from replacing the real diagnostic error.

These unlink() calls run inside encryptly failure handlers but are not guarded. If removing a partial .logd fails, control jumps to the outer catch and overwrites the original timeout/pack error with an “unexpected error”.

Proposed fix
-            if logd_path.exists():
-                logd_path.unlink()
+            try:
+                if logd_path.exists():
+                    logd_path.unlink()
+            except OSError as cleanup_err:
+                print(
+                    f"    {color('⚠', Colors.YELLOW)} Could not remove partial "
+                    f"{logd_path.name}: {cleanup_err}"
+                )
-            if logd_path.exists():
-                logd_path.unlink()
+            try:
+                if logd_path.exists():
+                    logd_path.unlink()
+            except OSError as cleanup_err:
+                print(
+                    f"    {color('⚠', Colors.YELLOW)} Could not remove partial "
+                    f"{logd_path.name}: {cleanup_err}"
+                )

Also applies to: 728-729

🤖 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 `@build.py` around lines 689 - 690, The unlink() call on logd_path is running
inside an error handler without protection, which means if the cleanup fails,
the exception will mask the original timeout/pack error. Wrap the
logd_path.exists() and logd_path.unlink() block (and the similar code at lines
728-729) in a try-except handler that catches exceptions during cleanup and logs
them without re-raising, ensuring the original error remains intact for the
outer error handler to process.

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

Propagate clean command failures.

clean_module() returns True even when the clean command exits non-zero, and main() ignores False results anyway. --clean can therefore print “Clean complete” and exit 0 while artifacts remain.

Proposed fix
-        subprocess.run(
+        result = subprocess.run(
             module.clean_cmd,
             cwd=str(module.dir),
             capture_output=not verbose,
             text=True,
             timeout=60,
             env=os.environ.copy(),
         )
+        if result.returncode != 0:
+            if not verbose:
+                output = "\n".join(
+                    part for part in [
+                        (result.stdout or "").strip(),
+                        (result.stderr or "").strip(),
+                    ]
+                    if part
+                )
+                if output:
+                    print(f"    {color('✗', Colors.RED)} Clean failed:\n{output}")
+            return False
         return True
         for module in selected:
             try:
-                clean_module(module, args.verbose)
+                if not clean_module(module, args.verbose):
+                    clean_errors.append((module.name, "clean failed"))
             except Exception as e:
                 clean_errors.append((module.name, str(e)))
                 print(f"    {color('✗', Colors.RED)} Error cleaning {module.name}: {e}")
@@
-        print(f"\n  {color('Clean complete.', Colors.GREEN)}")
-        return 0
+        if clean_errors:
+            print(f"\n  {color('Clean completed with errors.', Colors.YELLOW)}")
+            return 1
+        print(f"\n  {color('Clean complete.', Colors.GREEN)}")
+        return 0

Also applies to: 929-952

🤖 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 `@build.py` around lines 406 - 414, The clean_module() function unconditionally
returns True even when the subprocess.run() call for the clean command fails
with a non-zero exit code. Capture the result of subprocess.run() in the
clean_module() function and check its returncode attribute to return False when
the command fails (returncode != 0), otherwise return True on success.
Additionally, update main() (referenced at lines 929-952) to properly check the
return value from clean_module() and avoid printing "Clean complete" or exiting
with status 0 when the clean operation has actually failed.

69-79: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clean up the chunk that failed mid-write.

If write_bytes() creates or truncates chunk_path and then raises before chunks.append(chunk_path), the cleanup loop removes only prior chunks and leaves a corrupt *-partNNN.logd.

Proposed fix
                 except OSError as e:
                     print(f"    {color('✗', Colors.RED)} Failed to write chunk {index}: {e}")
-                    for chunk in chunks:
+                    for chunk in [*chunks, chunk_path]:
                         try:
-                            chunk.unlink()
+                            if chunk.exists():
+                                chunk.unlink()
                         except OSError:
                             pass
                     return [logd_path]
🤖 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 `@build.py` around lines 69 - 79, The exception handler for write_bytes()
failure in the chunk writing block removes only the chunks that were already
successfully appended to the chunks list, but if write_bytes() creates or
truncates chunk_path and then raises an exception before
chunks.append(chunk_path) executes, the partially written chunk_path file is
left behind. Modify the except OSError block to also attempt to remove
chunk_path itself using unlink() after cleaning up the prior chunks in the
cleanup loop, ensuring that the corrupt chunk file is also deleted even if the
write operation failed partway through.
🤖 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 `@build.py`:
- Around line 52-54: In the split_diagnostic_logd() function, when the source
log file does not exist (when not logd_path.exists()), the function should
return an empty list instead of returning [logd_path]. This prevents
generate_logd() from treating this as a successful result and writing metadata
that references a non-existent .logd file. Change the return statement to return
an empty list to properly indicate that no artifacts were produced.
- Around line 444-455: The run_cmd function does not have a timeout parameter
when calling subprocess.run(), which allows commands to hang indefinitely on
unhealthy mounts or stalled processes. Add a timeout parameter (with a sensible
default value like 30 seconds) to the subprocess.run() call, and extend the
exception handling block to catch subprocess.TimeoutExpired exceptions
separately. When a timeout occurs, return False with an appropriate error
message indicating the command timed out.
- Around line 689-690: The unlink() call on logd_path is running inside an error
handler without protection, which means if the cleanup fails, the exception will
mask the original timeout/pack error. Wrap the logd_path.exists() and
logd_path.unlink() block (and the similar code at lines 728-729) in a try-except
handler that catches exceptions during cleanup and logs them without re-raising,
ensuring the original error remains intact for the outer error handler to
process.
- Around line 406-414: The clean_module() function unconditionally returns True
even when the subprocess.run() call for the clean command fails with a non-zero
exit code. Capture the result of subprocess.run() in the clean_module() function
and check its returncode attribute to return False when the command fails
(returncode != 0), otherwise return True on success. Additionally, update main()
(referenced at lines 929-952) to properly check the return value from
clean_module() and avoid printing "Clean complete" or exiting with status 0 when
the clean operation has actually failed.
- Around line 69-79: The exception handler for write_bytes() failure in the
chunk writing block removes only the chunks that were already successfully
appended to the chunks list, but if write_bytes() creates or truncates
chunk_path and then raises an exception before chunks.append(chunk_path)
executes, the partially written chunk_path file is left behind. Modify the
except OSError block to also attempt to remove chunk_path itself using unlink()
after cleaning up the prior chunks in the cleanup loop, ensuring that the
corrupt chunk file is also deleted even if the write operation failed partway
through.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99a46d9f-87c0-49bc-8f00-57dccb06c5e2

📥 Commits

Reviewing files that changed from the base of the PR and between f7e9fe2 and 25e6bd3.

📒 Files selected for processing (1)
  • build.py

@GautamKumarOffical GautamKumarOffical force-pushed the feat/build-error-handling branch from 25e6bd3 to c2454c2 Compare June 19, 2026 11:11
@GautamKumarOffical

Copy link
Copy Markdown
Author

Fixed the merge conflict in build.py. Rebased the branch on upstream/main and resolved all conflicts. The build now runs cleanly.

@ghost ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the PR. I don't see the encrypted build diagnostic log artifact (*.logd / builds.compact) committed on this branch yet. Please rerun the build diagnostic/encryption step with the fixed encryptly tooling and commit the generated encrypted log artifact to this PR (typically under diagnostic/).

Blocking review until the encrypted diagnostic log artifact is present.

@GautamKumarOffical

Copy link
Copy Markdown
Author

Hey, fresh .logd diagnostic artifacts have been committed. Could you take another look?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
build.py (1)

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

Propagate clean failures to the --clean exit code.

clean_module() currently returns True even when the clean command exits non-zero, and main() ignores False returns plus artifact-removal failures. This can leave stale artifacts while still printing Clean complete. and exiting 0.

Proposed fix
-        run_text_process(
+        result = run_text_process(
             module.clean_cmd,
             cwd=str(module.dir),
             capture_output=not verbose,
             text=True,
             timeout=60,
             env=os.environ.copy(),
         )
+        if result.returncode != 0:
+            output = (result.stderr or result.stdout or "clean command failed").strip()
+            print(f"    {color('✗', Colors.RED)} Clean failed: {output}")
+            return False
         return True
-        clean_errors = []
+        clean_failed = False
         for module in selected:
             try:
-                clean_module(module, args.verbose)
+                if not clean_module(module, args.verbose):
+                    clean_failed = True
             except Exception as e:
-                clean_errors.append((module.name, str(e)))
+                clean_failed = True
                 print(f"    {color('✗', Colors.RED)} Error cleaning {module.name}: {e}")
@@
             except OSError as e:
+                clean_failed = True
                 print(f"  {color('⚠', Colors.YELLOW)} Could not remove {artifact.name}: {e}")
         print(f"\n  {color('Clean complete.', Colors.GREEN)}")
-        return 0
+        return 1 if clean_failed else 0

Also applies to: 1083-1108

🤖 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 `@build.py` around lines 489 - 497, The clean_module() function unconditionally
returns True even when the clean command fails, and main() ignores False returns
and artifact-removal failures. Modify clean_module() to capture and check the
result of run_text_process() and return False if the command fails or raises an
exception. Then in main() (the section mentioned in "Also applies to:
1083-1108"), ensure that False returns from clean_module() are checked and
propagated, artifact-removal failures are detected and handled, and the script
exits with a non-zero code when any part of the clean operation fails instead of
always printing "Clean complete." and exiting 0.
🧹 Nitpick comments (1)
build.py (1)

814-829: ⚡ Quick win

Use run_text_process() for the final encryptly pack call.

This raw subprocess.run(..., text=True) bypasses the UTF-8 decoding wrapper added in this PR, so locale-dependent decode failures can still surface on the main diagnostic-generation path.

Proposed fix
-            sr = subprocess.run(
+            sr = run_text_process(
                 [
                     str(encryptly_bin),
                     "pack",
                     str(logd_path),
🤖 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 `@build.py` around lines 814 - 829, Replace the raw subprocess.run call that
executes the encryptly pack command with the run_text_process() helper function.
The current implementation with text=True bypasses the UTF-8 decoding wrapper
that was introduced in this PR, allowing locale-dependent decode failures to
occur. Migrate the subprocess.run call that includes the encryptly_bin pack
arguments (logd_path, workspace, max-file-size) to use run_text_process()
instead, passing the same command arguments and timeout parameters to ensure
proper UTF-8 handling on the diagnostic-generation path.
🤖 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 `@build.py`:
- Around line 833-834: The unlink() calls in the error handlers can raise
OSError and mask the original diagnostic errors from encryptly timeout or
nonzero exit conditions. Wrap the logd_path.unlink() calls at both locations
(around lines 833-834 and 873-874) in try-except blocks that catch OSError to
make the cleanup best-effort operations. This ensures that if the unlink
operation fails, the original error being handled is preserved and not replaced
with a generic exception, allowing proper error diagnostics while keeping
cleanup as a non-critical operation.
- Around line 89-92: The return statement at line 92 in the logd file existence
check currently returns [logd_path] when the file does not exist, but this
causes generate_logd() to treat it as a success and write the nonexistent path
to diagnostic_logd. Change the return value from [logd_path] to an empty list []
so that the failure handling path at lines 893-903 can properly execute and
write diagnostic_logd_error instead.
- Around line 553-560: The hostname and user information captured in the
diagnostic bundle expose personally identifiable information that should not be
included in PR submissions by default. Replace the actual values from
platform.node() and getpass.getuser() with redacted placeholder values (such as
"redacted") instead of exposing the actual hostname and user. Consider adding an
opt-in mechanism (such as an environment variable or configuration flag) that
allows contributors to explicitly choose to include their actual local
identifiers if they prefer, but default to the redacted versions for privacy
protection.

---

Outside diff comments:
In `@build.py`:
- Around line 489-497: The clean_module() function unconditionally returns True
even when the clean command fails, and main() ignores False returns and
artifact-removal failures. Modify clean_module() to capture and check the result
of run_text_process() and return False if the command fails or raises an
exception. Then in main() (the section mentioned in "Also applies to:
1083-1108"), ensure that False returns from clean_module() are checked and
propagated, artifact-removal failures are detected and handled, and the script
exits with a non-zero code when any part of the clean operation fails instead of
always printing "Clean complete." and exiting 0.

---

Nitpick comments:
In `@build.py`:
- Around line 814-829: Replace the raw subprocess.run call that executes the
encryptly pack command with the run_text_process() helper function. The current
implementation with text=True bypasses the UTF-8 decoding wrapper that was
introduced in this PR, allowing locale-dependent decode failures to occur.
Migrate the subprocess.run call that includes the encryptly_bin pack arguments
(logd_path, workspace, max-file-size) to use run_text_process() instead, passing
the same command arguments and timeout parameters to ensure proper UTF-8
handling on the diagnostic-generation path.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7bb41f70-f907-4923-bb12-2b99a36387bc

📥 Commits

Reviewing files that changed from the base of the PR and between 25e6bd3 and f6cf2ee.

📒 Files selected for processing (3)
  • build.py
  • diagnostic/build-c2454c25.json
  • diagnostic/build-c2454c25.logd
✅ Files skipped from review due to trivial changes (1)
  • diagnostic/build-c2454c25.json

Comment thread build.py
Comment thread build.py
Comment on lines +553 to +560
try:
lines.append(f"hostname: {platform.node()}")
except Exception as e:
lines.append(f"hostname: unavailable ({e})")

try:
lines.append(f"user: {getpass.getuser()}")
except Exception as e:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact local user and host identifiers from PR diagnostics.

The diagnostic bundle is intended to be committed for PR review, and the metadata includes the decrypt password, so hostname and user become accessible in submitted artifacts. Prefer redacted values unless the contributor explicitly opts in to sharing local identifiers.

Proposed fix
-    try:
-        lines.append(f"hostname: {platform.node()}")
-    except Exception as e:
-        lines.append(f"hostname: unavailable ({e})")
-
-    try:
-        lines.append(f"user: {getpass.getuser()}")
-    except Exception as e:
-        lines.append(f"user: unavailable ({e})")
+    lines.append("hostname: redacted")
+    lines.append("user: redacted")
📝 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.

Suggested change
try:
lines.append(f"hostname: {platform.node()}")
except Exception as e:
lines.append(f"hostname: unavailable ({e})")
try:
lines.append(f"user: {getpass.getuser()}")
except Exception as e:
lines.append("hostname: redacted")
lines.append("user: redacted")
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 555-555: Do not catch blind exception: Exception

(BLE001)


[warning] 560-560: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@build.py` around lines 553 - 560, The hostname and user information captured
in the diagnostic bundle expose personally identifiable information that should
not be included in PR submissions by default. Replace the actual values from
platform.node() and getpass.getuser() with redacted placeholder values (such as
"redacted") instead of exposing the actual hostname and user. Consider adding an
opt-in mechanism (such as an environment variable or configuration flag) that
allows contributors to explicitly choose to include their actual local
identifiers if they prefer, but default to the redacted versions for privacy
protection.

Comment thread build.py
Comment on lines +833 to +834
if logd_path.exists():
logd_path.unlink()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard partial .logd cleanup in error handlers.

These unlink() calls can raise OSError while handling an encryptly timeout or nonzero exit, replacing the specific diagnostic error with the generic unexpected-error path. Keep cleanup best-effort here.

Proposed fix
             print(f"    {color('✗', Colors.RED)} {logd_path.relative_to(ROOT)} creation failed: {error}")
-            if logd_path.exists():
-                logd_path.unlink()
+            try:
+                if logd_path.exists():
+                    logd_path.unlink()
+            except OSError as unlink_err:
+                print(f"    {color('⚠', Colors.YELLOW)} Could not remove partial {logd_path.name}: {unlink_err}")
             if logd_path.exists():
-                logd_path.unlink()
+                try:
+                    logd_path.unlink()
+                except OSError as unlink_err:
+                    print(f"    {color('⚠', Colors.YELLOW)} Could not remove partial {logd_path.name}: {unlink_err}")

Also applies to: 873-874

🤖 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 `@build.py` around lines 833 - 834, The unlink() calls in the error handlers
can raise OSError and mask the original diagnostic errors from encryptly timeout
or nonzero exit conditions. Wrap the logd_path.unlink() calls at both locations
(around lines 833-834 and 873-874) in try-except blocks that catch OSError to
make the cleanup best-effort operations. This ensures that if the unlink
operation fails, the original error being handled is preserved and not replaced
with a generic exception, allowing proper error diagnostics while keeping
cleanup as a non-critical operation.

lobster-trap and others added 6 commits June 20, 2026 11:33
Signed-off-by: Gautam Kumar <gautamkumarofficial@users.noreply.github.com>
If logd_path.unlink() fails after chunks are written, the chunks
are now preserved and a warning is printed instead of silently
returning the original file path.

Signed-off-by: Gautam Kumar <gautamkumarofficial@users.noreply.github.com>
@GautamKumarOffical GautamKumarOffical force-pushed the feat/build-error-handling branch from f6cf2ee to 7c15568 Compare June 20, 2026 06:07
@GautamKumarOffical

Copy link
Copy Markdown
Author

Fixed merge conflict in build.py by rebasing onto upstream/main. The two conflicts in build_module were resolved by taking the run_text_process calls from upstream while preserving the variable references from this branch, and merging the PR's new FileNotFoundError/OSError handlers with upstream's more detailed timeout error messages. Regenerated the .logd diagnostic artifact with python3 build.py.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
build.py (1)

917-931: 💤 Low value

Consider using run_text_process for consistency.

This subprocess.run call doesn't use run_text_process, unlike the other subprocess calls updated in this PR. This means it won't have the deterministic UTF-8 encoding/errors handling. Consider aligning for consistency.

-        sr = subprocess.run(
+        sr = run_text_process(
             [
                 str(encryptly_bin),
                 "pack",
🤖 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 `@build.py` around lines 917 - 931, The subprocess.run call in the
encryptly_bin pack operation is not using the run_text_process helper function,
which is inconsistent with other subprocess calls in the PR and lacks
deterministic UTF-8 encoding/errors handling. Refactor the subprocess.run call
(which invokes encryptly_bin with pack, logd_path, include, and max-file-size
arguments) to use the run_text_process helper function instead, passing the
command list, cwd set to ROOT, and timeout of 300 as appropriate parameters to
maintain consistency with the rest of the codebase.
🤖 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 `@build.py`:
- Line 1176: Remove the f-string prefix from the print statement that outputs
"No modules selected." since the string contains no placeholder variables.
Change the f-prefixed string to a regular string literal by removing the f
character before the quotes in the print function call.

---

Nitpick comments:
In `@build.py`:
- Around line 917-931: The subprocess.run call in the encryptly_bin pack
operation is not using the run_text_process helper function, which is
inconsistent with other subprocess calls in the PR and lacks deterministic UTF-8
encoding/errors handling. Refactor the subprocess.run call (which invokes
encryptly_bin with pack, logd_path, include, and max-file-size arguments) to use
the run_text_process helper function instead, passing the command list, cwd set
to ROOT, and timeout of 300 as appropriate parameters to maintain consistency
with the rest of the codebase.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ebb8ba4-3746-4057-903d-c87ec981fc33

📥 Commits

Reviewing files that changed from the base of the PR and between f6cf2ee and 7c15568.

📒 Files selected for processing (5)
  • build.py
  • diagnostic/build-396da43c.json
  • diagnostic/build-396da43c.logd
  • diagnostic/build-c2454c25.json
  • diagnostic/build-c2454c25.logd
🚧 Files skipped from review as they are similar to previous changes (1)
  • diagnostic/build-c2454c25.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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: 1

🧹 Nitpick comments (1)
build.py (1)

917-931: 💤 Low value

Consider using run_text_process for consistency.

This subprocess.run call doesn't use run_text_process, unlike the other subprocess calls updated in this PR. This means it won't have the deterministic UTF-8 encoding/errors handling. Consider aligning for consistency.

-        sr = subprocess.run(
+        sr = run_text_process(
             [
                 str(encryptly_bin),
                 "pack",
🤖 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 `@build.py` around lines 917 - 931, The subprocess.run call in the
encryptly_bin pack operation is not using the run_text_process helper function,
which is inconsistent with other subprocess calls in the PR and lacks
deterministic UTF-8 encoding/errors handling. Refactor the subprocess.run call
(which invokes encryptly_bin with pack, logd_path, include, and max-file-size
arguments) to use the run_text_process helper function instead, passing the
command list, cwd set to ROOT, and timeout of 300 as appropriate parameters to
maintain consistency with the rest of the codebase.
🤖 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 `@build.py`:
- Line 1176: Remove the f-string prefix from the print statement that outputs
"No modules selected." since the string contains no placeholder variables.
Change the f-prefixed string to a regular string literal by removing the f
character before the quotes in the print function call.

---

Nitpick comments:
In `@build.py`:
- Around line 917-931: The subprocess.run call in the encryptly_bin pack
operation is not using the run_text_process helper function, which is
inconsistent with other subprocess calls in the PR and lacks deterministic UTF-8
encoding/errors handling. Refactor the subprocess.run call (which invokes
encryptly_bin with pack, logd_path, include, and max-file-size arguments) to use
the run_text_process helper function instead, passing the command list, cwd set
to ROOT, and timeout of 300 as appropriate parameters to maintain consistency
with the rest of the codebase.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ebb8ba4-3746-4057-903d-c87ec981fc33

📥 Commits

Reviewing files that changed from the base of the PR and between f6cf2ee and 7c15568.

📒 Files selected for processing (5)
  • build.py
  • diagnostic/build-396da43c.json
  • diagnostic/build-396da43c.logd
  • diagnostic/build-c2454c25.json
  • diagnostic/build-c2454c25.logd
🚧 Files skipped from review as they are similar to previous changes (1)
  • diagnostic/build-c2454c25.json
🛑 Comments failed to post (1)
build.py (1)

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

Remove extraneous f prefix from string without placeholders.

Static analysis flags this as F541 - the f-string has no placeholders.

-        print(f"  No modules selected.")
+        print("  No modules selected.")
📝 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.

        print("  No modules selected.")
🧰 Tools
🪛 Ruff (0.15.17)

[error] 1176-1176: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 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 `@build.py` at line 1176, Remove the f-string prefix from the print statement
that outputs "No modules selected." since the string contains no placeholder
variables. Change the f-prefixed string to a regular string literal by removing
the f character before the quotes in the print function call.

Source: Linters/SAST tools

@GautamKumarOffical

Copy link
Copy Markdown
Author

Hi, the encrypted .logd diagnostic artifact has been committed after the latest rebase. Could you please re-check? Thank you.

Soengkit referenced this pull request in Soengkit/frailbox-checkpoint Jun 20, 2026
Merged after owner review for fork bounty issue #8. Focused validation passed in a sparse checkout with cd market && go test ./analytics. The PR includes diagnostic/build-d8fc4651.json and diagnostic/build-d8fc4651.logd with the market module reported as PASS. Fixes #8.
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.

1 participant