Skip to content

fix: refresh in-progress workflow handling - #3

Merged
star-hengxing merged 8 commits into
mainfrom
fix/in-progress-refresh
Mar 1, 2026
Merged

fix: refresh in-progress workflow handling#3
star-hengxing merged 8 commits into
mainfrom
fix/in-progress-refresh

Conversation

@star-hengxing

@star-hengxing star-hengxing commented Mar 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • prioritize in-progress status in human report rendering to avoid stale conclusion misclassification
  • exclude in-progress workflows from llm success/failure aggregation and from the all-passed decision
  • add regression tests for conflicting state combinations (status=in_progress with conclusion=success)
  • make running-state human-report tests terminal-agnostic ([RUNNING] or hourglass icon)
  • show explicit empty-workflow reminders in both outputs when no workflows are found for the current commit
  • refetch workflows when ci-status.json exists but contains an empty array
  • update CI build workflow to run cargo test after cargo build

Summary by CodeRabbit

  • New Features

    • Added a visible RUNNING status indicator for in-progress workflows
    • Reports now explicitly show running workflows and exclude them from “all passed” summaries
  • Improvements

    • CI build now runs tests automatically after compilation
    • Reporting distinguishes running, successful, and failed workflows more clearly
    • Workflow refresh behavior refined to refresh when cache is missing, empty, or contains in-progress runs
  • Tests

    • Added unit tests covering refresh logic and report rendering
  • Documentation

    • Documented cache refresh rules and behavior in README

@coderabbitai

coderabbitai Bot commented Mar 1, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds detection and handling for in-progress workflows across models, reporting, and CLI fetch logic; report renderers treat running workflows specially and exclude them from "all passed" counts; CI build step now runs cargo test after cargo build; unit tests added for models, reports, and CLI helper.

Changes

Cohort / File(s) Summary
CI Workflow
/.github/workflows/build.yml
Replaced single-line cargo build command with a multi-line shell step that runs cargo build --workspace --all-targets --all-features then cargo test.
Workflow Status Logic
src/models.rs
Added pub fn is_in_progress(&self) -> bool; needs_job_refresh() now short-circuits true for in-progress statuses; added unit tests covering in-progress, completed, empty jobs, missing job IDs, and terminal cases.
Report Rendering
src/report.rs
Added internal icon_running(); updated render_human_report() and render_llm_report() to emit running indicators, exclude running workflows from all-passed counts, and handle empty input; added unit tests for human/LLM outputs.
CLI / Fetch Logic
src/main.rs
Added should_refresh_workflows() and async fn fetch_current_branch_workflows(...) -> Result<Vec<WorkflowStatus>>; unified fetch paths to use the new helper for cache-miss and refresh decisions; added tests for refresh logic.
Docs
README.md
Documented cache refresh rules describing when ci-status.json triggers a fetch vs. reuse of cache.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Main
    participant Cache as ci-status.json
    participant GH as GitHub API (Octocrab)
    participant Models as WorkflowStatus
    participant Report as report.rs

    CLI->>Cache: read cached statuses
    alt cache missing or empty
        CLI->>GH: fetch_current_branch_workflows(owner, repo, branch, sha)
        GH-->>CLI: workflow runs
        CLI->>Models: map runs -> WorkflowStatus
    else cache present
        CLI->>Models: parse cached JSON -> Vec<WorkflowStatus>
        CLI->>Models: should_refresh_workflows(cached)?
        alt needs refresh
            CLI->>GH: fetch_current_branch_workflows(...)
            GH-->>CLI: workflow runs
            CLI->>Models: map runs -> WorkflowStatus
        end
    end
    CLI->>Report: render_human_report(statuses)
    CLI->>Report: render_llm_report(statuses)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through runs both slow and spry,
I flag the RUNNING with a gleam in my eye.
I skip the still, cheer those that pass,
Fetch fresh when something's mid-flight en masse.
Hooray — carrot-coded status at last!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: refresh in-progress workflow handling' directly aligns with the main changes: adding explicit in-progress workflow handling in reports, prioritizing in-progress status in rendering, and ensuring cache refresh when workflows are in-progress.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/in-progress-refresh

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

Caution

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

⚠️ Outside diff range comments (1)
src/report.rs (1)

68-70: ⚠️ Potential issue | 🟠 Major

all workflows passed can be emitted while workflows are still running.

At Line 68 and Line 69, success/failure are counted from conclusion-only checks. Combined with Line 107, a running workflow with stale success conclusion can produce a false “all workflows passed”.

Suggested fix
-    let success = workflows.iter().filter(|w| w.is_success()).count();
-    let failure = workflows.iter().filter(|w| w.is_failure()).count();
+    let success = workflows
+        .iter()
+        .filter(|w| !w.is_in_progress() && w.is_success())
+        .count();
+    let failure = workflows
+        .iter()
+        .filter(|w| !w.is_in_progress() && w.is_failure())
+        .count();
     let other = total.saturating_sub(success + failure);
@@
-    for workflow in workflows.iter().filter(|w| w.is_failure()) {
+    for workflow in workflows
+        .iter()
+        .filter(|w| !w.is_in_progress() && w.is_failure())
+    {
@@
-    if failure == 0 && other == 0 {
+    let all_passed = workflows
+        .iter()
+        .all(|w| !w.is_in_progress() && w.is_success());
+    if all_passed {
         lines.push("all workflows passed".to_string());
     }

Also applies to: 77-78, 100-108

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

In `@src/report.rs` around lines 68 - 70, The code counts successes/failures using
conclusion-only predicates (is_success()/is_failure()) and can report "all
workflows passed" while some workflows are still running; update all places that
compute success/failure (the usages around workflows.iter() at the top and the
similar blocks at lines ~77-78 and ~100-108) to first restrict to concluded
workflows—e.g., filter with w.is_concluded() or w.status ==
WorkflowStatus::Completed before calling is_success()/is_failure()—so counts and
the “all workflows passed” decision only consider workflows that have finished.
🧹 Nitpick comments (1)
src/report.rs (1)

133-157: Add a regression test for conflicting status/conclusion.

Please add a case like status="in_progress" with conclusion=Some("success") to ensure report rendering keeps treating it as running.

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

In `@src/report.rs` around lines 133 - 157, Add a regression test that supplies a
workflow with status "in_progress" but conclusion Some("success") using the
existing helper workflow(...) and assert the renderer treats it as running; for
example, update or add to the tests using render_human_report and
render_llm_report to include workflow("FreeBSD","in_progress",Some("success"))
and assert the human report contains "- FreeBSD [RUNNING] (in_progress)" and the
LLM report does not contain "all workflows passed" and does contain "running
workflow=FreeBSD status=in_progress". Use the existing test names (e.g.,
renders_in_progress_workflow_as_running or
llm_report_does_not_mark_running_workflows_as_passed) or add a new test function
to keep behavior coverage for conflicting status/conclusion cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/report.rs`:
- Around line 32-53: The status check for in-progress workflows is currently
after conclusion checks, causing workflows with status == in_progress but stale
conclusion == success to render as passed; update the branching in report
generation so that workflow.is_in_progress() (or checking workflow.status ==
"in_progress") is evaluated before workflow.is_success() and
workflow.is_failure() — move the block that formats the running state (the code
using workflow.is_in_progress(), icon_running(), and workflow.status) to occur
prior to the success/failure blocks (or change the conditional order) so
in-progress workflows are rendered correctly.

---

Outside diff comments:
In `@src/report.rs`:
- Around line 68-70: The code counts successes/failures using conclusion-only
predicates (is_success()/is_failure()) and can report "all workflows passed"
while some workflows are still running; update all places that compute
success/failure (the usages around workflows.iter() at the top and the similar
blocks at lines ~77-78 and ~100-108) to first restrict to concluded
workflows—e.g., filter with w.is_concluded() or w.status ==
WorkflowStatus::Completed before calling is_success()/is_failure()—so counts and
the “all workflows passed” decision only consider workflows that have finished.

---

Nitpick comments:
In `@src/report.rs`:
- Around line 133-157: Add a regression test that supplies a workflow with
status "in_progress" but conclusion Some("success") using the existing helper
workflow(...) and assert the renderer treats it as running; for example, update
or add to the tests using render_human_report and render_llm_report to include
workflow("FreeBSD","in_progress",Some("success")) and assert the human report
contains "- FreeBSD [RUNNING] (in_progress)" and the LLM report does not contain
"all workflows passed" and does contain "running workflow=FreeBSD
status=in_progress". Use the existing test names (e.g.,
renders_in_progress_workflow_as_running or
llm_report_does_not_mark_running_workflows_as_passed) or add a new test function
to keep behavior coverage for conflicting status/conclusion cases.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9ab1bb and 28ee056.

📒 Files selected for processing (3)
  • .github/workflows/build.yml
  • src/models.rs
  • src/report.rs

Comment thread src/report.rs Outdated

@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)
src/report.rs (1)

145-155: Make running-icon tests terminal-agnostic.

The test assertions at lines 148 and 154 check only for [RUNNING], but icon_running() returns when supports_emoji() is true—which occurs when stdout is a terminal and the NO_EMOJI environment variable is unset. These tests will fail if run in an interactive terminal without NO_EMOJI set, despite correct behavior.

Harden the assertions to accept both patterns:

Proposed fix
         assert!(report.contains("- FreeBSD [RUNNING] (in_progress)"));
+        assert!(
+            report.contains("- FreeBSD [RUNNING] (in_progress)")
+                || report.contains("- FreeBSD ⏳ (in_progress)")
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/report.rs` around lines 145 - 155, The two tests
renders_in_progress_workflow_as_running and
renders_in_progress_with_success_conclusion_as_running assert only on the
literal "[RUNNING]" but icon_running() may return an emoji when supports_emoji()
is true; update these test assertions in src/report.rs (the two test functions
calling render_human_report and workflow) so they accept either the text marker
"[RUNNING]" or the emoji from icon_running() (call or reproduce both possible
outputs) — i.e., check the rendered report contains either "- FreeBSD [RUNNING]
(in_progress)" OR "- FreeBSD ⏳ (in_progress)" (or use icon_running() to build
the expected string) so the tests are terminal-agnostic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/report.rs`:
- Around line 116-120: The current computation of all_passed (let all_passed =
workflows.iter().all(|w| !w.is_in_progress() && w.is_success());) wrongly
returns true for an empty workflows iterator and causes lines.push("all
workflows passed".to_string()) to run on empty lists; change the guard so it
only pushes that message when there is at least one workflow and all of them
passed — e.g., require workflows.iter().next().is_some() (or
workflows.is_empty() == false) in conjunction with the existing all() check
before calling lines.push("all workflows passed".to_string()).

---

Nitpick comments:
In `@src/report.rs`:
- Around line 145-155: The two tests renders_in_progress_workflow_as_running and
renders_in_progress_with_success_conclusion_as_running assert only on the
literal "[RUNNING]" but icon_running() may return an emoji when supports_emoji()
is true; update these test assertions in src/report.rs (the two test functions
calling render_human_report and workflow) so they accept either the text marker
"[RUNNING]" or the emoji from icon_running() (call or reproduce both possible
outputs) — i.e., check the rendered report contains either "- FreeBSD [RUNNING]
(in_progress)" OR "- FreeBSD ⏳ (in_progress)" (or use icon_running() to build
the expected string) so the tests are terminal-agnostic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28ee056 and 7d3b4da.

📒 Files selected for processing (1)
  • src/report.rs

Comment thread src/report.rs Outdated

@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

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

Inline comments:
In `@src/main.rs`:
- Around line 83-88: The current logic treats any non-empty
cached_workflow_statuses as authoritative, causing run status/conclusion to stay
stale if a cached workflow is still "in_progress"; change the conditional so
that you call fetch_current_branch_workflows(&octocrab, &owner, &repo, &branch,
&commit_sha).await? not only when cached_workflow_statuses.is_empty() but also
when any cached workflow has status "in_progress" (e.g.,
cached_workflow_statuses.iter().any(|w| w.status == "in_progress")). This
ensures full run metadata (status/conclusion) is refreshed, keeping it in sync
with the separate jobs-refresh logic that currently updates only jobs.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d3b4da and 3955e43.

📒 Files selected for processing (2)
  • src/main.rs
  • src/report.rs

Comment thread src/main.rs Outdated

@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

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

Inline comments:
In `@src/main.rs`:
- Around line 81-85: The code currently fails hard when deserializing
cached_workflow_statuses via serde_json::from_str, but a malformed cache at
workflow_status_json_path should be treated as a cache miss; change the logic
around cached_workflow_statuses/WorkflowStatus to handle parse errors by
catching the serde_json::from_str result (e.g., match or Result::or_else) and on
Err log a warning and return an empty Vec<WorkflowStatus> so the tool refetches
instead of exiting (keep file read errors as fatal but recover from JSON parse
failures).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3955e43 and 34368ec.

📒 Files selected for processing (2)
  • README.md
  • src/main.rs

Comment thread src/main.rs
Comment on lines +81 to +85
let cached_workflow_statuses: Vec<WorkflowStatus> = serde_json::from_str(
&fs::read_to_string(&workflow_status_json_path)
.context("Failed to read existing ci-status.json")?,
)
.context("Failed to parse existing ci-status.json")?
.context("Failed to parse existing ci-status.json")?;

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

Recover from malformed cache instead of failing hard.

At Line 81-85, JSON parse failure exits the tool immediately. Since this file is a local cache artifact, invalid JSON should be treated as a cache miss and trigger a refetch.

💡 Proposed fix
-        let cached_workflow_statuses: Vec<WorkflowStatus> = serde_json::from_str(
-            &fs::read_to_string(&workflow_status_json_path)
-                .context("Failed to read existing ci-status.json")?,
-        )
-        .context("Failed to parse existing ci-status.json")?;
-
-        if should_refresh_workflows(&cached_workflow_statuses) {
-            output_mode.emit_verbose(
-                "Cached ci-status.json requires workflow refresh; refreshing workflows...",
-            );
-            fetch_current_branch_workflows(&octocrab, &owner, &repo, &branch, &commit_sha).await?
-        } else {
-            cached_workflow_statuses
-        }
+        let cached_raw = fs::read_to_string(&workflow_status_json_path)
+            .context("Failed to read existing ci-status.json")?;
+        match serde_json::from_str::<Vec<WorkflowStatus>>(&cached_raw) {
+            Ok(cached_workflow_statuses) => {
+                if should_refresh_workflows(&cached_workflow_statuses) {
+                    output_mode.emit_verbose(
+                        "Cached ci-status.json requires workflow refresh; refreshing workflows...",
+                    );
+                    fetch_current_branch_workflows(&octocrab, &owner, &repo, &branch, &commit_sha)
+                        .await?
+                } else {
+                    cached_workflow_statuses
+                }
+            }
+            Err(err) => {
+                output_mode.emit_verbose(format!(
+                    "Cached ci-status.json is invalid ({err}); refreshing workflows..."
+                ));
+                fetch_current_branch_workflows(&octocrab, &owner, &repo, &branch, &commit_sha)
+                    .await?
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 81 - 85, The code currently fails hard when
deserializing cached_workflow_statuses via serde_json::from_str, but a malformed
cache at workflow_status_json_path should be treated as a cache miss; change the
logic around cached_workflow_statuses/WorkflowStatus to handle parse errors by
catching the serde_json::from_str result (e.g., match or Result::or_else) and on
Err log a warning and return an empty Vec<WorkflowStatus> so the tool refetches
instead of exiting (keep file read errors as fatal but recover from JSON parse
failures).

@star-hengxing
star-hengxing merged commit 407349b into main Mar 1, 2026
4 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.

1 participant