Skip to content

fix(ci): make wall-clock time advisory in the performance gate - #338

Merged
docdyhr merged 1 commit into
mainfrom
fix/perf-gate-time-advisory
Oct 19, 2026
Merged

docdyhr merged 1 commit into
mainfrom
fix/perf-gate-time-advisory

Conversation

@docdyhr

@docdyhr docdyhr commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

This is a follow-up to #337. That PR moved the weekly performance gate to best-of-N and made recommend/outdated warn-only. The first run after it merged (dispatched run 39243572918) still failed, on apps:

Run (vs the 2026-10-11 baseline) apps best-of-3 recommend best-of-3 outdated best-of-3
2026-10-18 (39174628747) +8% +26% +61%
2026-10-19 (39243572918) +104% +28% +120%

There was no runtime change. apps shells out to system_profiler, whose duration on a fresh macOS CI VM depends on the machine's state. #337 was validated on only one pair of runs, and that pair happened to be quiet. Wall-clock time on shared runners can't tell a regression from runner noise.

Changes

  • scripts/compare_performance.py:
    • All relative time changes are warnings ("time is advisory (shared-runner noise)").
    • The gate fails on what noise can't produce: a memory regression beyond 20%, or a benchmark whose best iteration (min_time, falling back to avg_time) exceeds the 30 s ceiling. The ceiling is enforced even when there's no baseline. One slow iteration can't trip it.
    • The memory absolute limit (500 MB) is still a warning, as before.
  • tests/test_compare_performance.py: 8 tests, including replays of the 10-18 and 10-19 runs, which now pass with warnings. 6 of them fail on the fix(ci): make the weekly performance gate tolerate runner and network noise #337 version.
  • tests/test_project_consistency.py: the baseline-promotion check now finds the regression step by its python scripts/compare_performance.py call, not its display name.
  • performance.yml: the step label states the new rule.
  • CHANGELOG.md: fix(ci): make the weekly performance gate tolerate runner and network noise #337's Unreleased entry is rewritten to describe the final behaviour.

Verification

  • Full suite: 2841 passed, 16 skipped.
  • ruff check, ruff format --check, mypy and the pre-commit hooks are clean.
  • After merge, run gh workflow run performance.yml once. Expect a pass, with time warnings for apps/recommend/outdated if the runner is slow.

🤖 Generated with Claude Code

Summary by Sourcery

Make the performance gate distinguish actionable memory and severe runtime failures from wall-clock noise on shared CI runners.

Bug Fixes:

  • Make the weekly performance gate resilient to shared-runner timing noise by treating wall-clock regressions as warnings.
  • Continue failing performance checks for memory regressions above 20% and best benchmark times exceeding the 30-second ceiling, including runs without a baseline.

Enhancements:

  • Use the best iteration for absolute time-limit checks so a single slow iteration does not fail the gate.
  • Update the workflow messaging and changelog to document the revised performance-gate behavior.

Tests:

  • Add coverage for replayed noisy CI runs, advisory time regressions, memory failures, absolute time ceilings, missing baselines, and legacy timing data.
  • Make the performance baseline-promotion consistency check identify the comparison step by its command rather than its display name.

#337 switched the gate to best-of-N and warned only on the network-bound
benchmarks, but the first post-merge run (39243572918) still failed:
`apps` (system_profiler) measured +104% best-of-3 against the 2026-10-11
baseline, after +8% on 2026-10-18, with no runtime change. Wall-clock on
shared macOS runners cannot tell a regression from runner noise.

- Relative time changes are warnings ("time is advisory").
- The gate fails on what noise cannot produce: a memory regression beyond
  20%, or a benchmark whose best iteration (min_time, else avg_time)
  exceeds the 30 s ceiling — also enforced when there is no baseline.
- tests/test_compare_performance.py: 8 tests incl. replays of the 10-18 and
  10-19 runs; 6 of them fail on the #337 version.
- tests/test_project_consistency.py: locate the regression-check step by its
  `python scripts/compare_performance.py` invocation, not its display name
  (the step label now states the new rule).

Full suite: 2841 passed, 16 skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Sorry @docdyhr, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 8 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T18:00:10.074077Z 70a70e9 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR changes the performance gate from wall-clock regression enforcement to advisory time reporting, while preserving hard failures for memory regressions and best-of-N runtimes over 30 seconds; tests, workflow metadata, consistency checks, and changelog documentation are updated accordingly.

Flow diagram for the advisory performance gate

flowchart TD
    Results[Performance results] --> Limits[Check best time and memory limits]
    Limits -->|Best time over 30s| Fail[Fail gate]
    Limits -->|Memory over 500MB| WarnMemory[Warn only]
    Limits --> Baseline{Baseline available?}
    Baseline -->|No| Pass[Pass gate]
    Baseline -->|Yes| Compare[Compare against baseline]
    Compare -->|Memory regression over 20%| Fail
    Compare -->|Time regression| WarnTime[Warn: time is advisory]
    Compare -->|No gated regression| Pass
Loading

File-Level Changes

Change Details Files
Make wall-clock performance changes advisory while retaining hard safeguards for memory regressions and extreme runtimes.
  • Convert all relative time regressions into warnings labeled as shared-runner noise.
  • Fail on memory increases above 20%.
  • Enforce a 30-second ceiling using min_time, with avg_time fallback for legacy results.
  • Apply the absolute time ceiling even when no baseline is available; keep the 500 MB memory limit warning-only.
scripts/compare_performance.py
Update performance-gate coverage to validate noisy-run behavior and the remaining failure conditions.
  • Replay the October 18 and 19 runs and assert they pass with time warnings.
  • Cover memory regressions, best-time ceiling failures, slow-iteration tolerance, legacy fallback, and no-baseline enforcement.
  • Replace obsolete network-bound and time-regression failure expectations.
tests/test_compare_performance.py
Make the baseline-promotion consistency check resilient to workflow step-label changes.
  • Locate the comparison step by its Python invocation rather than its display name.
  • Assert that exactly one matching workflow step exists.
tests/test_project_consistency.py
Document the revised performance-gate policy in CI configuration and project history.
  • Rename the workflow step to describe memory and best-time gating with advisory time deltas.
  • Rewrite the unreleased changelog entry to reflect the final shared-runner-noise behavior and safeguards.
.github/workflows/performance.yml
CHANGELOG.md

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

@github-actions

Copy link
Copy Markdown

🔒 Security Analysis Report

Security Analysis Report

Generated: Mon Oct 19 17:59:17 UTC 2026
Repository: docdyhr/versiontracker
Commit: 79eb909

Bandit Security Scan

�[?25l
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  0%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  5%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  5%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 25%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 34%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 41%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━�[0m �[35m 52%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━�[0m �[35m 64%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━�[0m �[35m 73%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━�[0m �[35m 80%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━�[0m �[35m 86%�[0m �[36m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[?25hRun started:2026-10-19 17:59:19.154189+00:00

Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b608_hardcoded_sql_expressions.html
   Location: versiontracker/advanced_cache.py:693:24
692	                # Use f-string for better readability
693	                msg = f"Failed to delete from cache {key}: {e}"
694	                raise CacheError(msg) from e

--------------------------------------------------
>> Issue: [B105:hardcoded_password_string] Possible hardcoded password: 'homebrew_token'
   Severity: Low   Confidence: Medium
   CWE: CWE-259 (https://cwe.mitre.org/data/definitions/259.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b105_hardcoded_password_string.html
   Location: versiontracker/audit/models.py:161:21
160	    BUNDLE_ID = "bundle_id"
161	    HOMEBREW_TOKEN = "homebrew_token"
162	    DISPLAY_NAME = "display_name"

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/config.py:174:16
173	                cmd = f"{path} --version"
174	                import subprocess
175	

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/config.py:177:29
176	                try:
177	                    result = subprocess.run(cmd.split(), capture_output=True, timeout=2, check=False)
178	                    returncode = result.returncode

--------------------------------------------------
>> Issue: [B110:try_except_pass] Try, Except, Pass detected.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b110_try_except_pass.html
   Location: versiontracker/experimental/analytics.py:645:16
644	                    self.peak_cpu = max(self.peak_cpu, cpu_percent)
645	                except Exception:
646	                    pass
647	                time.sleep(0.05)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:518:8
517	        # Type assertion: apps and brews cannot be None here due to exit_code checks above
518	        assert apps is not None
519	        assert brews is not None

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:519:8
518	        assert apps is not None
519	        assert brews is not None
520	        apps = _filter_applications(apps, brews, include_brews)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:539:8
538	        # Type assertion: outdated_info cannot be None here due to exit_code check
539	        assert outdated_info is not None
540	        # Type cast: outdated_info cannot be None here due to exit_code check above

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/homebrew.py:12:0
11	import re
12	import subprocess
13	import time

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/macos_integration.py:12:0
11	import plistlib
12	import subprocess
13	from pathlib import Path

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/menubar_app.py:8:0
7	import logging
8	import subprocess
9	import sys

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/plugins/example_plugins.py:322:16
321	            try:
322	                import subprocess
323	

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/utils.py:16:0
15	import shutil
16	import subprocess
17	import sys

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:804:17
803	    try:
804	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
805	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:804:17
803	    try:
804	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
805	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:820:17
819	    try:
820	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
821	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:820:17
819	    try:
820	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
821	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:853:15
852	    try:
853	        return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=check)
854	    except subprocess.TimeoutExpired as e:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/batch.py:11:0
10	import logging
11	import subprocess
12	from concurrent.futures import ThreadPoolExecutor

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/homebrew.py:18:0
17	import logging
18	import subprocess
19	

--------------------------------------------------

Code scanned:
	Total lines of code: 17221
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 30

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 21
		Medium: 1
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 1
		Medium: 1
		High: 20
Files skipped (0):

Pip-Audit Results

�[?25l�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting aiohappyeyeballs (2.7.1)
�[2K�[32m\�[0m Auditing aiohappyeyeballs (2.7.1)
�[2K�[32m\�[0m Collecting aiohttp (3.14.5)
�[2K�[32m\�[0m Collecting aiohttp (3.14.5)
�[2K�[32m\�[0m Auditing aiohttp (3.14.5)
�[2K�[32m\�[0m Collecting aiosignal (1.4.0)
�[2K�[32m\�[0m Auditing aiosignal (1.4.0)
�[2K�[32m\�[0m Collecting ast_serialize (0.13.1)
�[2K�[32m\�[0m Auditing ast_serialize (0.13.1)
�[2K�[32m\�[0m Collecting attrs (26.1.0)
�[2K�[32m\�[0m Auditing attrs (26.1.0)
�[2K�[32m\�[0m Collecting bandit (1.9.4)
�[2K�[32m\�[0m Auditing bandit (1.9.4)
�[2K�[32m\�[0m Collecting boolean.py (5.0)
�[2K�[32m\�[0m Auditing boolean.py (5.0)
�[2K�[32m\�[0m Collecting build (1.6.1)
�[2K�[32m\�[0m Auditing build (1.6.1)
�[2K�[32m\�[0m Collecting CacheControl (0.14.4)
�[2K�[32m\�[0m Auditing CacheControl (0.14.4)
�[2K�[32m\�[0m Collecting certifi (2026.10.18)
�[2K�[32m\�[0m Auditing certifi (2026.10.18)
�[2K�[32m\�[0m Collecting cffi (2.1.1)
�[2K�[32m\�[0m Auditing cffi (2.1.1)
�[2K�[32m\�[0m Collecting charset-normalizer (3.5.2)
�[2K�[32m\�[0m Auditing charset-normalizer (3.5.2)
�[2K�[32m\�[0m Collecting coverage (7.16.2)
�[2K�[32m\�[0m Auditing coverage (7.16.2)
�[2K�[32m\�[0m Collecting cryptography (50.0.2)
�[2K�[32m\�[0m Auditing cryptography (50.0.2)
�[2K�[32m\�[0m Collecting cyclonedx-python-lib (11.12.0)
�[2K�[32m\�[0m Auditing cyclonedx-python-lib (11.12.0)
�[2K�[32m\�[0m Collecting defusedxml (0.7.1)
�[2K�[32m\�[0m Auditing defusedxml (0.7.1)
�[2K�[32m\�[0m Collecting docutils (0.23)
�[2K�[32m\�[0m Auditing docutils (0.23)
�[2K�[32m\�[0m Collecting filelock (4.2.4)
�[2K�[32m\�[0m Auditing filelock (4.2.4)
�[2K�[32m\�[0m Collecting frozenlist (1.8.0)
�[2K�[32m\�[0m Auditing frozenlist (1.8.0)
�[2K�[32m\�[0m Collecting id (1.6.1)
�[2K�[32m\�[0m Auditing id (1.6.1)
�[2K�[32m\�[0m Collecting idna (3.20)
�[2K�[32m\�[0m Auditing idna (3.20)
�[2K�[32m\�[0m Collecting iniconfig (2.3.1)
�[2K�[32m\�[0m Auditing iniconfig (2.3.1)
�[2K�[32m\�[0m Collecting jaraco.classes (3.4.0)
�[2K�[32m\�[0m Auditing jaraco.classes (3.4.0)
�[2K�[32m\�[0m Collecting jaraco.context (6.1.2)
�[2K�[32m\�[0m Auditing jaraco.context (6.1.2)
�[2K�[32m\�[0m Collecting jaraco.functools (4.6.0)
�[2K�[32m\�[0m Auditing jaraco.functools (4.6.0)
�[2K�[32m\�[0m Collecting jeepney (0.9.0)
�[2K�[32m\�[0m Collecting jeepney (0.9.0)
�[2K�[32m\�[0m Auditing jeepney (0.9.0)
�[2K�[32m\�[0m Collecting keyring (25.7.0)
�[2K�[32m\�[0m Auditing keyring (25.7.0)
�[2K�[32m\�[0m Collecting librt (0.16.0)
�[2K�[32m\�[0m Auditing librt (0.16.0)
�[2K�[32m\�[0m Collecting license-expression (30.4.4)
�[2K�[32m\�[0m Auditing license-expression (30.4.4)
�[2K�[32m\�[0m Collecting macversiontracker (1.2.0)
�[2K�[32m\�[0m Auditing macversiontracker (1.2.0)
�[2K�[32m\�[0m Collecting markdown-it-py (4.2.0)
�[2K�[32m\�[0m Auditing markdown-it-py (4.2.0)
�[2K�[32m\�[0m Collecting mdurl (0.1.2)
�[2K�[32m\�[0m Auditing mdurl (0.1.2)
�[2K�[32m\�[0m Collecting more-itertools (11.2.1)
�[2K�[32m\�[0m Auditing more-itertools (11.2.1)
�[2K�[32m\�[0m Collecting msgpack (1.2.3)
�[2K�[32m\�[0m Auditing msgpack (1.2.3)
�[2K�[32m\�[0m Collecting multidict (7.1.0)
�[2K�[32m\�[0m Auditing multidict (7.1.0)
�[2K�[32m\�[0m Collecting mypy (2.4.0)
�[2K�[32m\�[0m Auditing mypy (2.4.0)
�[2K�[32m\�[0m Collecting mypy_extensions (1.1.0)
�[2K�[32m\�[0m Auditing mypy_extensions (1.1.0)
�[2K�[32m\�[0m Collecting nh3 (0.3.7)
�[2K�[32m\�[0m Auditing nh3 (0.3.7)
�[2K�[32m\�[0m Collecting packageurl-python (0.17.6)
�[2K�[32m\�[0m Auditing packageurl-python (0.17.6)
�[2K�[32m\�[0m Collecting packaging (26.3)
�[2K�[32m\�[0m Auditing packaging (26.3)
�[2K�[32m\�[0m Collecting pathspec (1.1.1)
�[2K�[32m\�[0m Auditing pathspec (1.1.1)
�[2K�[32m\�[0m Collecting pip (26.2.1)
�[2K�[32m\�[0m Auditing pip (26.2.1)
�[2K�[32m\�[0m Collecting pip_api (0.0.35)
�[2K�[32m|�[0m Auditing pip_api (0.0.35)
�[2K�[32m|�[0m Collecting pip_audit (2.10.1)
�[2K�[32m|�[0m Auditing pip_audit (2.10.1)
�[2K�[32m|�[0m Collecting pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Auditing pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Collecting platformdirs (4.13.5)
�[2K�[32m|�[0m Auditing platformdirs (4.13.5)
�[2K�[32m|�[0m Collecting pluggy (1.7.0)
�[2K�[32m|�[0m Auditing pluggy (1.7.0)
�[2K�[32m|�[0m Collecting propcache (0.5.4)
�[2K�[32m|�[0m Auditing propcache (0.5.4)
�[2K�[32m|�[0m Collecting psutil (7.2.2)
�[2K�[32m|�[0m Auditing psutil (7.2.2)
�[2K�[32m|�[0m Collecting py-serializable (2.1.0)
�[2K�[32m|�[0m Auditing py-serializable (2.1.0)
�[2K�[32m|�[0m Collecting pycparser (3.11)
�[2K�[32m|�[0m Auditing pycparser (3.11)
�[2K�[32m|�[0m Collecting Pygments (2.21.0)
�[2K�[32m|�[0m Auditing Pygments (2.21.0)
�[2K�[32m|�[0m Collecting pyparsing (3.3.3)
�[2K�[32m|�[0m Collecting pyparsing (3.3.3)
�[2K�[32m|�[0m Auditing pyparsing (3.3.3)
�[2K�[32m|�[0m Collecting pyproject_hooks (1.3.3)
�[2K�[32m|�[0m Auditing pyproject_hooks (1.3.3)
�[2K�[32m|�[0m Collecting pytest (9.1.1)
�[2K�[32m|�[0m Auditing pytest (9.1.1)
�[2K�[32m|�[0m Collecting pytest-asyncio (1.4.0)
�[2K�[32m|�[0m Auditing pytest-asyncio (1.4.0)
�[2K�[32m|�[0m Collecting pytest-cov (7.1.0)
�[2K�[32m|�[0m Auditing pytest-cov (7.1.0)
�[2K�[32m|�[0m Collecting pytest-mock (3.17.0)
�[2K�[32m|�[0m Auditing pytest-mock (3.17.0)
�[2K�[32m|�[0m Collecting pytest-timeout (2.4.0)
�[2K�[32m|�[0m Auditing pytest-timeout (2.4.0)
�[2K�[32m|�[0m Collecting PyYAML (6.0.3)
�[2K�[32m|�[0m Auditing PyYAML (6.0.3)
�[2K�[32m|�[0m Collecting readme_renderer (46.0)
�[2K�[32m|�[0m Auditing readme_renderer (46.0)
�[2K�[32m|�[0m Collecting requests (2.34.2)
�[2K�[32m|�[0m Auditing requests (2.34.2)
�[2K�[32m|�[0m Collecting requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Auditing requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Collecting rfc3986 (2.0.0)
�[2K�[32m|�[0m Auditing rfc3986 (2.0.0)
�[2K�[32m|�[0m Collecting rich (15.0.0)
�[2K�[32m|�[0m Auditing rich (15.0.0)
�[2K�[32m|�[0m Collecting ruff (0.17.0)
�[2K�[32m|�[0m Auditing ruff (0.17.0)
�[2K�[32m|�[0m Collecting SecretStorage (3.5.0)
�[2K�[32m|�[0m Auditing SecretStorage (3.5.0)
�[2K�[32m|�[0m Collecting setuptools (84.0.0)
�[2K�[32m|�[0m Auditing setuptools (84.0.0)
�[2K�[32m|�[0m Collecting sortedcontainers (2.4.0)
�[2K�[32m|�[0m Auditing sortedcontainers (2.4.0)
�[2K�[32m|�[0m Collecting stevedore (5.9.1)
�[2K�[32m|�[0m Auditing stevedore (5.9.1)
�[2K�[32m|�[0m Collecting tabulate (0.10.0)
�[2K�[32m|�[0m Auditing tabulate (0.10.0)
�[2K�[32m|�[0m Collecting termcolor (3.3.0)
�[2K�[32m|�[0m Auditing termcolor (3.3.0)
�[2K�[32m|�[0m Collecting tomli (2.5.0)
�[2K�[32m|�[0m Auditing tomli (2.5.0)
�[2K�[32m|�[0m Collecting tomli_w (1.2.0)
�[2K�[32m|�[0m Auditing tomli_w (1.2.0)
�[2K�[32m|�[0m Collecting tqdm (4.70.1)
�[2K�[32m|�[0m Auditing tqdm (4.70.1)
�[2K�[32m|�[0m Collecting twine (7.0.0)
�[2K�[32m|�[0m Auditing twine (7.0.0)
�[2K�[32m|�[0m Collecting types-PyYAML (6.0.12.20260906)
�[2K�[32m|�[0m Auditing types-PyYAML (6.0.12.20260906)
�[2K�[32m|�[0m Collecting typing_extensions (4.16.0)
�[2K�[32m|�[0m Auditing typing_extensions (4.16.0)
�[2K�[32m|�[0m Collecting urllib3 (2.8.0)
�[2K�[32m|�[0m Auditing urllib3 (2.8.0)
�[2K�[32m|�[0m Collecting wheel (0.48.0)
�[2K�[32m|�[0m Auditing wheel (0.48.0)
�[2K�[32m|�[0m Collecting yarl (1.25.1)
�[2K�[32m|�[0m Auditing yarl (1.25.1)
�[2K�[32m|�[0m Auditing yarl (1.25.1)
�[2K�[32m|�[0m Auditing yarl (1.25.1)
�[?25h
�[1A�[2K```

@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: 70a70e92a1

ℹ️ 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 +78 to +79
if label == "time":
warnings.append(f" WARNING: {detail}; time is advisory (shared-runner noise)")

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 Restore an effective relative regression signal

When a real runtime regression remains below 30 seconds, this now makes it advisory even though the purported memory fallback cannot currently detect anything: scripts/performance_test.py:51-64 calls disable_profiling() before generate_report(), while PerformanceProfiler.report() in versiontracker/profiling.py:201-208 returns an empty dict whenever profiling is disabled, so every produced avg_memory_mb is zero. Consequently ordinary regressions always pass and can be promoted as the next baseline; either fix the producer's memory measurement in this change or retain a gated relative signal.

Useful? React with 👍 / 👎.

retention-days: 30

- name: Compare with baseline (fail on >20% regression)
- name: Compare with baseline (fails on memory regression or a best time over 30s; time deltas advisory)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the documented performance-gate semantics

After this behavior change, CONTRIBUTING.md:281-292 still tells contributors that relative avg_time regressions fail the workflow and that operations over 30 seconds are warning-only. The new gate does the opposite for both cases, so anyone using the repository's performance instructions will misinterpret results and acceptance criteria; update that section alongside the workflow label.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Oct 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@docdyhr
docdyhr merged commit d357b53 into main Oct 19, 2026
25 checks passed
@docdyhr
docdyhr deleted the fix/perf-gate-time-advisory branch October 19, 2026 18:14
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