Skip to content

fix(pr-agent): classify timeouts per ATTEMPT, not on total job time - #34

Open
yakimoto wants to merge 1 commit into
mainfrom
fix/pr-agent-per-attempt-timeout
Open

fix(pr-agent): classify timeouts per ATTEMPT, not on total job time#34
yakimoto wants to merge 1 commit into
mainfrom
fix/pr-agent-per-attempt-timeout

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

User description

Re-syncs this repo to wave-foundation-public#72, which landed after the inline pr-agent lane was adopted here. Tracked as wave-pen#417.

The defect

The adopted template stamped AGENT_START once, before attempt 1, then compared total job time — attempt 1 + the 45s backoff + attempt 2 — against STEP_BUDGET_S=360, a budget its own comment calls per-attempt.

Two healthy-but-slow attempts (~180s each, ~405s together) therefore reported:

pr-agent TIMED OUT … A hang, NOT a rate limit.

…sending the next reader to debug a hang that never happened. The else-branch lied the other way, asserting the run was "well inside the budget" from the same misused total.

Found by qodo review on wave-monitor#48 and confirmed against the file before acting.

The fix

Stamp each attempt separately and classify on the longest attempt, with if: always() end stamps so an attempt killed by its step timeout still records one — exactly the case the classifier exists to catch. Total wall time is still reported as context but no longer decides the verdict.

case now before
180s + 180s (405s total) failed after 2 attempts TIMED OUT
attempt killed at ~358s TIMED OUT

Not urgent, not ignorable

The defect is in a message, not behaviour — the lane still retries, still renders NEUTRAL, still never blocks a PR. But that verdict step exists precisely because "a confidently wrong cause is worse than no cause", so a classifier that can misname a hang defeats its own purpose.

Job id pr_agent and every on: trigger unchanged — the job id is the check-run context and branch protection matches on it.

Refs wave-av/wave-pen#417, wave-av/wave-pen#388


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Note

Low Risk
CI diagnostic-message fix only; retry, timeouts, and non-blocking check behavior are unchanged.

Overview
Fixes a misclassification in the pr-agent verdict step: two slow-but-healthy attempts plus backoff (~405s) were labeled as a hang because total job time was compared to the per-attempt 360s budget.

Each attempt now gets its own start/end stamps (if: always() so a timeout-killed step still records an end). The classifier uses the longest attempt, with 15s slack for runner kill lag. Wall time is still logged but no longer decides hang vs rate-limit. Retry and NEUTRAL-never-block behavior are unchanged.

Reviewed by Cursor Bugbot for commit cf58dad. Bugbot is set up for automated code reviews on this repo. Configure here.


CodeAnt-AI Description

Classify PR-Agent timeouts using individual attempts

What Changed

  • Timeout messages now measure the longest review attempt instead of the total run, so retries and backoff do not appear to be a single hung attempt
  • Each attempt records its start and end time, including attempts stopped by their step timeout
  • Failed retries now report both attempt durations and total wall time while correctly identifying errors that finished within the per-attempt budget
  • Near-timeout attempts receive a small timing allowance so runner shutdown does not hide a real timeout

Impact

✅ Accurate timeout explanations
✅ Fewer false hang reports after retries
✅ Clearer distinction between timeouts and upstream errors

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by Sourcery

Classify PR-Agent failures using per-attempt durations to prevent healthy retries from being reported as hangs while preserving advisory, non-blocking behavior.

Bug Fixes:

  • Correct timeout classification so failed PR-Agent retries are evaluated by the duration of the longest individual attempt rather than total job time.
  • Record start and end times for each retry attempt, including attempts terminated by step timeouts, and account for runner shutdown timing when identifying timeouts.

Enhancements:

  • Retain total wall-clock duration as diagnostic context while distinguishing per-attempt timeouts from upstream failures.

Re-syncs this repo to wave-foundation-public#72, which landed after the inline
lane was adopted here.

THE DEFECT. The adopted template stamped AGENT_START once, before attempt 1,
then compared TOTAL job time — attempt 1 + the 45s backoff + attempt 2 —
against STEP_BUDGET_S=360, a budget its own comment calls PER-ATTEMPT. Two
healthy-but-slow attempts (~180s each, ~405s together) therefore reported

  "pr-agent TIMED OUT ... A hang, NOT a rate limit."

sending the next reader to debug a hang that never happened; the else-branch
lied the other way, asserting the run was "well inside the budget" from the
same misused total.

Found by qodo review on wave-monitor#48 and confirmed against the file before
acting.

THE FIX. Stamp each attempt separately and classify on the LONGEST attempt,
with if: always() end stamps so an attempt killed BY its step timeout still
records one — exactly the case the classifier exists to catch. Total wall time
is still reported as context but no longer decides the verdict.

NOT URGENT, NOT IGNORABLE. The defect is in a MESSAGE, not in behaviour: the
lane still retries, still renders NEUTRAL, still never blocks a PR. But that
verdict step exists precisely because "a confidently wrong cause is worse than
no cause", so shipping a classifier that can misname a hang defeats its purpose.

Job id pr_agent and every on: trigger unchanged — the job id is the check-run
context and branch protection matches on it.

Refs wave-av/wave-pen#417, wave-av/wave-pen#388
@codeant-ai

codeant-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR cf58dad Aug 24, 2026 · 13:38 13:38

@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_97a53224-61a8-4e10-8559-ab0d899d57a8)

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 29 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 91 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 087a7bca-8711-48b3-8fdf-71743cc05463

📥 Commits

Reviewing files that changed from the base of the PR and between 826951c and cf58dad.

📒 Files selected for processing (1)
  • .github/workflows/pr-agent.yml

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

@codeant-ai codeant-ai Bot added the size:M This PR changes 30-99 lines, ignoring generated files label Aug 24, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 24, 2026

Copy link
Copy Markdown

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — This is a self-contained CI fix that corrects timeout classification from total job duration to the longest individual attempt. Existing triggers, retries, neutral rendering, and application behavior remain unchanged.

Not approved because:

  • Credit balance exhausted. Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix pr-agent timeout classification to use per-attempt duration

🐞 Bug fix ⚙️ Configuration changes 🕐 10-20 Minutes

Grey Divider

AI Description

• Stamp start/end timestamps for each pr-agent attempt (including timed-out attempts)
• Classify failures using the longest attempt duration, not total job wall time
• Keep total wall time as context, but prevent misleading “hang” vs “rate-limit” messages
Diagram

graph TD
  J["pr_agent workflow job"] --> A1["Attempt 1 (stamp start/end)"] --> B["Backoff 45s"] --> A2["Attempt 2 (stamp start/end)"] --> C{"Classify by longest attempt"}
  C --> T["Warn: TIMED OUT"]
  C --> F["Warn: failed after 2 attempts"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Wrap both attempts in one script step
  • ➕ All timing is local to one process (simpler arithmetic and variables)
  • ➕ No need to shuttle timestamps via GITHUB_ENV across steps
  • ➖ A step killed by GitHub timeout may not run cleanup reliably; you can still miss an end stamp
  • ➖ Re-implementing retry/backoff logic in bash is harder to maintain than native workflow steps
2. Use continue-on-error and always-run a post-step
  • ➕ Cleaner control flow: agent steps don’t short-circuit the job
  • ➕ Centralized verdict logic always executes
  • ➖ Still requires per-attempt timing to avoid the original misclassification
  • ➖ Can make genuine failures easier to accidentally ignore if not carefully handled

Recommendation: The PR’s approach is the best fit for GitHub Actions constraints: stamping start/end per attempt and running end stamps with if: always() maximizes the chance of recording timing even when an attempt fails or is killed by its step timeout. Given GitHub’s lack of a first-class per-step “timed_out” signal, classifying via per-attempt elapsed time (with small slack) is the most reliable and least invasive solution.

Files changed (1) +44 / -6

Bug fix (1) +44 / -6
pr-agent.ymlMeasure per-attempt durations and fix timeout verdict messaging +44/-6

Measure per-attempt durations and fix timeout verdict messaging

• Replaces single-run start stamping with per-attempt start/end stamps (including 'if: always()' end stamps). Updates the verdict logic to classify timeouts based on the longest attempt duration (with slack), while still reporting total wall time for context and hardening arithmetic defaults to avoid classifier failures.

.github/workflows/pr-agent.yml

@gitar-bot

gitar-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by September 1. Add seats for more headroom.
Learn more

Code Review ✅ Approved

Refactors PR-agent timeout classification to measure per attempt rather than cumulative job time, preventing false hang reports on retried runs. No issues found.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Timeout slack too large 🐞 Bug ≡ Correctness
Description
The verdict step treats any longest-attempt duration ≥ (STEP_BUDGET_S - 15s) as “TIMED OUT”, which
can misclassify genuine long-running failures (e.g., an error at ~350s) as a hang/step-timeout kill.
This undermines the PR’s stated goal of avoiding confidently-wrong root-cause messages.
Code

.github/workflows/pr-agent.yml[R213-217]

+          # SLACK because a step killed AT its timeout records a hair under the
+          # budget — the runner's kill is not instantaneous.
+          SLACK=15
+          if [ "$LONGEST" -ge $(( STEP_BUDGET_S - SLACK )) ]; then
+            echo "::warning::pr-agent TIMED OUT — the longest attempt ran ${LONGEST}s against a ${STEP_BUDGET_S}s per-attempt budget (attempt 1 ${A1}s, attempt 2 ${A2}s), so it was killed by its step timeout rather than returning an error. A hang, NOT a rate limit. Rendering NEUTRAL: an advisory reviewer must not block the PR (#3128)."
Evidence
The workflow subtracts a fixed 15s from the per-attempt timeout budget when deciding to emit the
“TIMED OUT” warning, so any failure lasting 345–359s (with STEP_BUDGET_S=360) is reported as a
timeout kill even if it actually returned an error before the timeout. The code explicitly sets
SLACK=15 and uses it in the comparison.

.github/workflows/pr-agent.yml[181-182]
.github/workflows/pr-agent.yml[213-217]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The verdict classifier declares a timeout when `LONGEST >= STEP_BUDGET_S - SLACK` with `SLACK=15`. This can label a non-timeout failure that happens late (but still before the step timeout) as “killed by step timeout / hang”, reintroducing the misclassification the PR is trying to fix.

### Issue Context
The workflow uses wall-clock stamps around each attempt and then classifies failures based on attempt duration.

### Fix Focus Areas
- `.github/workflows/pr-agent.yml[213-217]`

Suggested direction:
- Reduce `SLACK` to a very small value (e.g., 1–3s), or
- Use a two-tier message: only say “killed by step timeout” when `LONGEST >= STEP_BUDGET_S`, and use a less definitive message for near-timeout durations.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unconditional attempt2 end stamp 🐞 Bug ☼ Reliability
Description
The workflow stamps ATTEMPT2_END with if: always() even when attempt 2 did not start, and the
verdict computes A2 = ATTEMPT2_END - ATTEMPT2_START defaulting missing START to 0. If
ATTEMPT2_START is absent for any reason while ATTEMPT2_END is set, A2 becomes an epoch-sized number
and forces a bogus “TIMED OUT” classification.
Code

.github/workflows/pr-agent.yml[R167-169]

+      - name: stamp attempt 2 end
+        if: always()
+        run: echo "ATTEMPT2_END=$(date +%s)" >> "$GITHUB_ENV"
Evidence
The workflow writes ATTEMPT2_END in an always-running step, and later computes A2 by subtracting
ATTEMPT2_START defaulting missing values to 0. This creates an invalid huge duration if START is
missing but END exists, which then feeds LONGEST and the timeout classification.

.github/workflows/pr-agent.yml[167-169]
.github/workflows/pr-agent.yml[206-212]
.github/workflows/pr-agent.yml[211-217]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ATTEMPT2_END` is recorded unconditionally, but `A2` is computed as `ATTEMPT2_END - ATTEMPT2_START` with missing values defaulting to 0. If `ATTEMPT2_END` is present but `ATTEMPT2_START` is not, `A2` becomes extremely large and makes `LONGEST` exceed the timeout threshold, producing a wrong verdict.

### Issue Context
Attempt-2 steps are conditional on attempt-1 failure, but the end-stamp step is currently unconditional.

### Fix Focus Areas
- `.github/workflows/pr-agent.yml[136-139]`
- `.github/workflows/pr-agent.yml[167-169]`
- `.github/workflows/pr-agent.yml[206-212]`

Suggested direction:
- Change “stamp attempt 2 end” to only run when attempt 2 was actually entered (e.g., `if: always() && steps.agent.outcome == 'failure'`).
- Make `A2` compute only when both start and end stamps are present; otherwise force `A2=0`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 2 rules
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +213 to +217
# SLACK because a step killed AT its timeout records a hair under the
# budget — the runner's kill is not instantaneous.
SLACK=15
if [ "$LONGEST" -ge $(( STEP_BUDGET_S - SLACK )) ]; then
echo "::warning::pr-agent TIMED OUT — the longest attempt ran ${LONGEST}s against a ${STEP_BUDGET_S}s per-attempt budget (attempt 1 ${A1}s, attempt 2 ${A2}s), so it was killed by its step timeout rather than returning an error. A hang, NOT a rate limit. Rendering NEUTRAL: an advisory reviewer must not block the PR (#3128)."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Timeout slack too large 🐞 Bug ≡ Correctness

The verdict step treats any longest-attempt duration ≥ (STEP_BUDGET_S - 15s) as “TIMED OUT”, which
can misclassify genuine long-running failures (e.g., an error at ~350s) as a hang/step-timeout kill.
This undermines the PR’s stated goal of avoiding confidently-wrong root-cause messages.
Agent Prompt
### Issue description
The verdict classifier declares a timeout when `LONGEST >= STEP_BUDGET_S - SLACK` with `SLACK=15`. This can label a non-timeout failure that happens late (but still before the step timeout) as “killed by step timeout / hang”, reintroducing the misclassification the PR is trying to fix.

### Issue Context
The workflow uses wall-clock stamps around each attempt and then classifies failures based on attempt duration.

### Fix Focus Areas
- `.github/workflows/pr-agent.yml[213-217]`

Suggested direction:
- Reduce `SLACK` to a very small value (e.g., 1–3s), or
- Use a two-tier message: only say “killed by step timeout” when `LONGEST >= STEP_BUDGET_S`, and use a less definitive message for near-timeout durations.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +167 to +169
- name: stamp attempt 2 end
if: always()
run: echo "ATTEMPT2_END=$(date +%s)" >> "$GITHUB_ENV"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Unconditional attempt2 end stamp 🐞 Bug ☼ Reliability

The workflow stamps ATTEMPT2_END with if: always() even when attempt 2 did not start, and the
verdict computes A2 = ATTEMPT2_END - ATTEMPT2_START defaulting missing START to 0. If
ATTEMPT2_START is absent for any reason while ATTEMPT2_END is set, A2 becomes an epoch-sized number
and forces a bogus “TIMED OUT” classification.
Agent Prompt
### Issue description
`ATTEMPT2_END` is recorded unconditionally, but `A2` is computed as `ATTEMPT2_END - ATTEMPT2_START` with missing values defaulting to 0. If `ATTEMPT2_END` is present but `ATTEMPT2_START` is not, `A2` becomes extremely large and makes `LONGEST` exceed the timeout threshold, producing a wrong verdict.

### Issue Context
Attempt-2 steps are conditional on attempt-1 failure, but the end-stamp step is currently unconditional.

### Fix Focus Areas
- `.github/workflows/pr-agent.yml[136-139]`
- `.github/workflows/pr-agent.yml[167-169]`
- `.github/workflows/pr-agent.yml[206-212]`

Suggested direction:
- Change “stamp attempt 2 end” to only run when attempt 2 was actually entered (e.g., `if: always() && steps.agent.outcome == 'failure'`).
- Make `A2` compute only when both start and end stamps are present; otherwise force `A2=0`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

✅ Merged (0) · ☑ Fixed (0)

Process

  • No fixes were applied (no_fixes_applied)

@sourcery-ai sourcery-ai 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.

Sorry @yakimoto, you have reached your weekly rate limit of 250000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates the pr-agent workflow’s diagnostic classifier to measure the longest individual attempt against the per-attempt timeout budget, preventing healthy retries plus backoff from being misreported as hangs while preserving existing retry and non-blocking behavior.

Sequence diagram for per-attempt timeout classification

sequenceDiagram
    participant Workflow
    participant Agent as PR-Agent
    participant Classifier

    Workflow->>Workflow: stamp attempt 1 start
    Workflow->>Agent: run attempt 1
    Agent-->>Workflow: success or failure
    Workflow->>Workflow: stamp attempt 1 end
    alt attempt 1 failed
        Workflow->>Workflow: sleep 45s
        Workflow->>Workflow: stamp attempt 2 start
        Workflow->>Agent: run attempt 2
        Agent-->>Workflow: success or failure
        Workflow->>Workflow: stamp attempt 2 end
    end
    Workflow->>Classifier: calculate A1, A2, and LONGEST
    alt LONGEST >= STEP_BUDGET_S - 15
        Classifier-->>Workflow: report TIMED OUT and render NEUTRAL
    else neither attempt reached budget
        Classifier-->>Workflow: report failed/rate-limit likely and render NEUTRAL
    end
Loading

File-Level Changes

Change Details Files
Track and classify timeout duration independently for each PR-Agent retry attempt.
  • Replace the single job-level start timestamp with per-attempt start timestamps.
  • Add always() end stamps so timeout-killed attempts retain duration data.
  • Compute the longest attempt and compare it with the per-attempt budget, including 15 seconds of runner-shutdown slack.
  • Continue reporting total wall time as context without using it for the timeout verdict.
.github/workflows/pr-agent.yml
Clarify diagnostic messages while preserving advisory and retry behavior.
  • Report both attempt durations and wall-clock duration in failure warnings.
  • Distinguish per-attempt timeout failures from upstream errors/rate limits.
  • Keep retry conditions, job identity, triggers, NEUTRAL rendering, and non-blocking behavior unchanged.
.github/workflows/pr-agent.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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

Labels

size:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant