Skip to content

fix: stabilize Uploaded, timeouts, and evidence redaction#3

Merged
EffortlessSteven merged 1 commit into
mainfrom
stabilize-0.2.1
Feb 16, 2026
Merged

fix: stabilize Uploaded, timeouts, and evidence redaction#3
EffortlessSteven merged 1 commit into
mainfrom
stabilize-0.2.1

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

  • Timeout correctness: fix cargo_publish timeout path — avoid try_wait + wait_with_output double-consuming stdout/stderr pipes; read pipes manually after try_wait succeeds; on timeout, capture output before killing the process. Propagate timeout via CargoOutput.timed_out so parallel mode still performs registry reconciliation.
  • Redaction safety: use to_ascii_lowercase() instead of to_lowercase() to avoid Unicode offset mismatches during case-insensitive redaction. Remove dead val_offset variable.
  • Uploaded semantics: Uploaded is intermediate, not success — remove it from success predicates. Add fallback version_exists check after the retry loop in both serial and parallel engines so that packages stuck in the Uploaded state are reconciled against the registry.

Tests

  • cargo test --workspace — 296 tests pass (263 lib + 13 CLI + 20 e2e)
  • New tests: redact_mixed_case_authorization, redact_mixed_case_token, redact_non_ascii_near_sensitive_pattern_no_panic
  • Updated: resume-from-Uploaded test now exercises the readiness verification path

Copilot AI review requested due to automatic review settings February 16, 2026 10:06
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @EffortlessSteven, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the robustness and correctness of the package publishing process. It addresses critical issues related to command execution timeouts, ensuring that process output is always captured and timeout status is accurately reported. Furthermore, it refines sensitive data redaction for improved security and corrects the handling of the 'Uploaded' package state, preventing false positives and ensuring proper registry reconciliation. These changes collectively lead to a more reliable and secure publishing workflow.

Highlights

  • Timeout Handling Refinement: The cargo_publish function's timeout mechanism has been improved to correctly capture stdout/stderr pipes by reading them manually after try_wait succeeds, avoiding issues with wait_with_output double-consuming. Additionally, output is now captured before killing a timed-out process, and a timed_out flag is propagated for better error handling and registry reconciliation.
  • Redaction Safety Enhancement: Sensitive data redaction now uses to_ascii_lowercase() instead of to_lowercase() to prevent Unicode offset mismatches during case-insensitive pattern matching, improving the reliability of security-sensitive operations. A dead val_offset variable was also removed.
  • Uploaded State Semantics Correction: The Uploaded package state is no longer considered a final success state. It has been removed from success predicates, and a fallback version_exists check has been added after the retry loop in both serial and parallel engines to ensure packages stuck in an Uploaded state are properly reconciled against the registry.
  • New Redaction Tests: Added new unit tests (redact_mixed_case_authorization, redact_mixed_case_token, redact_non_ascii_near_sensitive_pattern_no_panic) to specifically validate the improved redaction logic against mixed-case and non-ASCII inputs.
  • Updated Resume-from-Uploaded Test: The existing test for resuming from an Uploaded state has been updated to explicitly exercise and verify the new readiness verification path, ensuring the system correctly transitions packages to Published.
Changelog
  • crates/shipper/src/cargo.rs
    • Added std::io::Read trait import for manual pipe reading.
    • Introduced a timed_out boolean field to the CargoOutput struct to indicate if a command execution timed out.
    • Replaced to_lowercase() with to_ascii_lowercase() in redaction logic for Authorization and token patterns to avoid Unicode issues.
    • Removed the unused val_offset variable from the redaction function.
    • Refactored cargo_publish to manually read stdout/stderr pipes when a child process exits or times out, ensuring output capture.
    • Modified cargo_publish to return the new timed_out status.
    • Updated cargo_publish_dry_run_workspace and cargo_publish_dry_run_package to include timed_out: false in their CargoOutput return.
    • Added three new tests (redact_mixed_case_authorization, redact_mixed_case_token, redact_non_ascii_near_sensitive_pattern_no_panic) for redaction functionality.
  • crates/shipper/src/engine.rs
    • Removed PackageState::Uploaded from the success predicates in the run_publish function, so it's no longer considered a final success.
    • Implemented a fallback check after the retry loop for packages in PackageState::Uploaded, verifying their existence on the registry via version_exists.
    • Adjusted the resume-from-Uploaded test to expect PackageState::Published directly and to assert that readiness verification was exercised.
  • crates/shipper/src/engine_parallel.rs
    • Modified the cargo_succeeded condition to explicitly check !out.timed_out in addition to out.exit_code == 0.
    • Added a fallback check for PackageState::Uploaded in the parallel engine's publish_package function, similar to the serial engine, to confirm registry visibility.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Added timeout tracking for cargo publish operations with improved error handling
    • Introduced registry visibility verification to ensure packages are confirmed as published before marking as complete
  • Bug Fixes

    • Enhanced redaction of sensitive information in logs, including tokens and authorization headers with case-insensitive pattern matching
    • Improved package publication status tracking to only mark packages as successfully published when verified on the registry

Walkthrough

The changes add timeout tracking to cargo publish operations with enhanced output redaction, while tightening publish success verification logic in the engine by eliminating Uploaded as a final success state and introducing post-loop registry visibility checks when packages reach the Uploaded state.

Changes

Cohort / File(s) Summary
Timeout tracking and redaction
crates/shipper/src/cargo.rs
Introduces timed_out field to CargoOutput struct. Implements timeout detection by killing child processes and capturing remaining output with exit code -1. Expands redaction pipeline with redact_sensitive function and case-insensitive pattern matching for sensitive environment variables (CARGO_REGISTRY_TOKEN, CARGO_REGISTRIES__TOKEN).
Publish success and registry verification
crates/shipper/src/engine.rs, crates/shipper/src/engine_parallel.rs
Narrows publish completion condition to exclude Uploaded state; only Published or Skipped count as success. Adds post-loop registry visibility checks when packages remain Uploaded, transitioning to Published if version exists on registry or raising Ambiguous error if visibility cannot be confirmed. Tightens cargo success condition to require exit code 0 and not timed out.

Sequence Diagram(s)

sequenceDiagram
    participant Engine as Engine
    participant Package as Package<br/>(State)
    participant Registry as Registry
    participant Result as Result<br/>(Outcome)

    Note over Engine,Result: Publish Loop Completes

    Engine->>Package: Check state
    alt Package in Uploaded state
        Engine->>Registry: Query: version exists?
        alt Version exists
            Registry-->>Engine: Yes
            Engine->>Package: Transition to Published
            Engine->>Result: Return success receipt
        else Version not found
            Registry-->>Engine: No
            Engine->>Result: Set error (Ambiguous visibility)
            Engine->>Package: Transition to Failed
        end
    else Package in other final state
        Engine->>Result: Return based on state
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Based on the provided data, no additional related PRs were identified beyond the current changes.

Poem

🐰 Hops through timeouts with care,
Redacts secrets from the air,
Checks if uploads made it right,
Registry glows—all's in sight! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main changes: timeout handling, evidence redaction safety, and Uploaded state semantics adjustments.
Description check ✅ Passed The description comprehensively details all major changes across the codebase with concrete technical explanations and test information.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stabilize-0.2.1

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces several important fixes for stability and correctness. The timeout handling in cargo_publish is now more robust, capturing output even on timeouts. The redaction logic is safer against Unicode-related panics by using to_ascii_lowercase. The semantics of the Uploaded state have been clarified, with fallback checks to prevent packages from getting stuck. The changes are well-reasoned and include corresponding tests. I have a few suggestions to further improve maintainability and error handling in the new logic.

.get(&key)
.map(|p| p.state.clone());
if matches!(current_state, Some(PackageState::Uploaded)) {
if reg.version_exists(&p.name, &p.version).unwrap_or(false) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using unwrap_or(false) here suppresses potential errors from version_exists, such as network issues or unexpected API responses. This could lead to incorrectly reporting a package as not visible when the check itself failed. It would be better to propagate the error using the ? operator, similar to the logic in the serial engine (crates/shipper/src/engine.rs). The function signature of publish_package already supports returning a Result.

Suggested change
if reg.version_exists(&p.name, &p.version).unwrap_or(false) {
if reg.version_exists(&p.name, &p.version)? {

Comment on lines +139 to +146
let mut stdout_bytes = Vec::new();
let mut stderr_bytes = Vec::new();
if let Some(mut out) = child.stdout.take() {
let _ = out.read_to_end(&mut stdout_bytes);
}
if let Some(mut err) = child.stderr.take() {
let _ = err.read_to_end(&mut stderr_bytes);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There's duplicated logic for reading stdout and stderr pipes here and in the timeout handling block (lines 158-165). To improve maintainability, you could extract this logic into a local helper function or closure.

Additionally, the Result from read_to_end is being ignored. It would be good practice to handle this, even if it's just to log a warning on failure, as an I/O error could occur while reading the pipes.

{
let mut state = st.lock().unwrap();
update_state_locked(&mut state, &key, PackageState::Published);
let _ = state::save_state(state_dir, &state);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The result of state::save_state is being ignored. If saving the state fails (e.g., due to file system permissions or I/O errors), the error will be silently dropped. This could lead to an inconsistent state. It's better to propagate the error using the ? operator.

Suggested change
let _ = state::save_state(state_dir, &state);
state::save_state(state_dir, &state)?;

@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 (2)
crates/shipper/src/engine.rs (1)

700-710: ⚠️ Potential issue | 🟠 Major

Inconsistency: serial path still treats Uploaded as success, contradicting the PR's stated semantic change.

The parallel path (line 323) correctly excludes Uploaded from the success predicate:

PackageState::Published | PackageState::Skipped { .. }

But the serial path here still includes Uploaded:

PackageState::Published | PackageState::Uploaded | PackageState::Skipped { .. }

While the new post-loop reconciliation (lines 619–632) should ensure no receipt reaches this point in Uploaded state, keeping Uploaded in the success predicate contradicts the PR's objective of treating it as an intermediate state, and silently masks any regression where the reconciliation logic is bypassed.

Proposed fix: align serial path with parallel path
     let exec_result = if receipts.iter().all(|r| {
         matches!(
             r.state,
-            PackageState::Published | PackageState::Uploaded | PackageState::Skipped { .. }
+            PackageState::Published | PackageState::Skipped { .. }
         )
     }) {
crates/shipper/src/engine_parallel.rs (1)

427-456: ⚠️ Potential issue | 🟡 Minor

Event log reports hardcoded ErrorClass::Ambiguous regardless of original error class.

At line 444, the PackageFailed event always uses ErrorClass::Ambiguous, even though class (from last_err) could be a different variant (e.g., Retryable). The state update at lines 428–435 correctly uses the original class, but the event log entry diverges.

Proposed fix: use the actual error class in the event
                 let mut log = event_log.lock().unwrap();
                 log.record(PublishEvent {
                     timestamp: Utc::now(),
                     event_type: EventType::PackageFailed {
-                        class: ErrorClass::Ambiguous,
+                        class: class.clone(),
                         message: msg.clone(),
                     },
                     package: pkg_label,

Note: this requires keeping class alive past the update_state_locked call. You'll need to clone it before the state update or restructure the borrows.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 662e51998f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +160 to +164
if let Some(mut out) = child.stdout.take() {
let _ = out.read_to_end(&mut stdout_bytes);
}
if let Some(mut err) = child.stderr.take() {
let _ = err.read_to_end(&mut stderr_bytes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid blocking timeout path on pipe drain

After timing out, this branch kills and waits for cargo, then does a blocking read_to_end on the captured pipes. If cargo publish spawned children that inherited stdout/stderr and outlived the killed parent (a realistic case in build/publish flows), EOF is delayed until those descendants exit, so the timeout handler can hang far past per_package_timeout and stall parallel workers.

Useful? React with 👍 / 👎.

Copilot AI 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.

Pull request overview

This PR fixes three related issues: timeout handling in cargo_publish, Unicode-safe redaction, and the semantics of the Uploaded state.

Changes:

  • Fixed timeout handling to avoid double-consuming stdout/stderr pipes and to propagate timeout status via the timed_out field in CargoOutput
  • Changed redaction from to_lowercase() to to_ascii_lowercase() to preserve byte offsets when slicing strings, preventing panics with non-ASCII characters
  • Added fallback registry checks in both serial and parallel engines to reconcile packages stuck in Uploaded state, and removed Uploaded from the parallel mode success predicate

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
crates/shipper/src/cargo.rs Added timed_out field to CargoOutput; fixed timeout path to read pipes manually after killing process; switched redaction to to_ascii_lowercase() for Unicode safety; added tests for mixed-case and non-ASCII patterns
crates/shipper/src/engine.rs Removed Uploaded from parallel mode success check (line 323); added fallback version_exists check after retry loop for packages in Uploaded state (lines 619-632); updated test expectations
crates/shipper/src/engine_parallel.rs Added timeout check to success condition (line 222); added fallback version_exists check for packages in Uploaded state (lines 371-393)

@EffortlessSteven EffortlessSteven merged commit 577246d into main Feb 16, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants