Skip to content

test: fix flaky concurrent e2e test β€” thread-safe sys.argv patching#144

Merged
docdyhr merged 1 commit intomasterfrom
fix/e2e-concurrent-test-thread-safe-argv
May 1, 2026
Merged

test: fix flaky concurrent e2e test β€” thread-safe sys.argv patching#144
docdyhr merged 1 commit intomasterfrom
fix/e2e-concurrent-test-thread-safe-argv

Conversation

@docdyhr
Copy link
Copy Markdown
Owner

@docdyhr docdyhr commented May 1, 2026

Summary

  • mock.patch("sys.argv") inside each thread body is not thread-safe: threads racing to enter/exit the context manager corrupt the save/restore chain, exposing the real pytest argv to versiontracker_main(), which caused argparse to fail with "unrecognized arguments: tests/test_end_to_end_integration.py --timeout=900 --tb=long --capture=no" β€” manifesting as assert 2 == 3 (one thread raised, never appended)
  • Fix: hoist both sys.argv and _get_apps_data patches outside the thread spawn loop, matching the pattern from fix: flaky e2e tests, performance threshold, and exceptions/error_codes coverageΒ #134–test: fix two more flaky e2e tests with proper handler-level mocksΒ #136
  • Also adds explicit exception capture (errors list) and a thread.is_alive() assertion so future failures produce actionable messages instead of a count mismatch
  • Bumps .python-version 3.13.3 β†’ 3.13.13

Test plan

  • test_concurrent_operations_workflow β€” passes locally (was the failing test)
  • Full e2e class β€” 22/22 passed
  • Full suite β€” 2338 passed, 16 skipped, 0 failures

πŸ€– Generated with Claude Code

Summary by Sourcery

Stabilize the concurrent end-to-end workflow test by making its mocking fully thread-safe and strengthening failure diagnostics.

Tests:

  • Guard the concurrent workflow test against thread-safety issues by hoisting shared mocks out of worker threads and asserting on thread liveness and captured exceptions.

Chores:

  • Update the pinned Python version in .python-version from 3.13.3 to 3.13.13.

…eads

mock.patch("sys.argv") inside each thread body is not thread-safe: threads
racing to enter/exit the context manager corrupt the save/restore chain and
expose the real pytest argv to versiontracker_main(), which made argparse
reject the test runner arguments as unrecognized CLI flags.

Fix: hoist both sys.argv and _get_apps_data patches outside the thread spawn
loop, matching the existing pattern established in #134-136. Also capture
thread exceptions into an errors list and assert threads completed within the
timeout, so future failures surface a clear message instead of a count mismatch.

Also bumps .python-version from 3.13.3 β†’ 3.13.13 (pyenv minor update).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 1, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Refactors a concurrent end-to-end test to use thread-safe patching of sys.argv and app data, adds better diagnostics for thread failures, and updates the Python version pin.

File-Level Changes

Change Details Files
Make the concurrent e2e workflow test thread-safe and more debuggable.
  • Wrap versiontracker_main calls in a try/except within the thread target to collect exceptions into a shared errors list instead of silently failing via count mismatch.
  • Hoist sys.argv and _get_apps_data monkeypatches out of the thread function into a single context manager around thread creation and joining to avoid non-thread-safe mock.patch usage.
  • After joining each thread with a timeout, assert that the thread is no longer alive to surface hangs as explicit test failures.
  • Add an assertion that no exceptions were captured from worker threads and keep existing assertions that three results are produced and all exit codes are zero.
  • Update the .python-version file to bump Python from 3.13.3 to 3.13.13.
tests/test_end_to_end_integration.py
.python-version

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

github-actions Bot commented May 1, 2026

πŸ”’ Security Analysis Report

Security Analysis Report

Generated: Fri May 1 09:27:16 UTC 2026
Repository: docdyhr/versiontracker
Commit: 3413949

Bandit Security Scan

οΏ½[?25l
οΏ½[2KWorking... οΏ½[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m  0%οΏ½[0m οΏ½[36m-:--:--οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m  6%οΏ½[0m οΏ½[36m-:--:--οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━�[0mοΏ½[91mβ•ΈοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m 15%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m 26%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m 33%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━━━━━━�[0m οΏ½[35m 43%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━━━━━━�[0mοΏ½[91mβ•ΈοΏ½[0mοΏ½[90m━━━━━━━━━━━━━━━━━�[0m οΏ½[35m 57%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━━━━�[0m οΏ½[35m 69%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━━━━�[0m οΏ½[35m 76%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━━━━━�[0m οΏ½[35m 83%οΏ½[0m οΏ½[36m0:00:01οΏ½[0m
οΏ½[2KWorking... οΏ½[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0mοΏ½[90mβ•ΊοΏ½[0mοΏ½[90m━━�[0m οΏ½[35m 93%οΏ½[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-05-01 09:27:17.864818+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:610:24
609	                # Use f-string for better readability
610	                msg = f"Failed to delete from cache {key}: {e}"
611	                raise CacheError(msg) from 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/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:493:8
492	        # Type assertion: apps and brews cannot be None here due to exit_code checks above
493	        assert apps is not None
494	        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:494:8
493	        assert apps is not None
494	        assert brews is not None
495	        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:511:8
510	        # Type assertion: outdated_info cannot be None here due to exit_code check
511	        assert outdated_info is not None
512	        # 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:11:0
10	import os
11	import subprocess
12	from pathlib import Path

--------------------------------------------------
>> 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/macos_integration.py:245:21
244	            # nosec B603 - osascript with controlled arguments
245	            result = subprocess.run(cmd, capture_output=True, text=True)
246	

--------------------------------------------------
>> 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:15:0
14	import shutil
15	import subprocess
16	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:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        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:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        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:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        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:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        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:833:15
832	    try:
833	        return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=check)
834	    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: 15020
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 33

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

Safety Check Results



οΏ½[33mοΏ½[1m+===========================================================================================================================================================================================+οΏ½[0m


οΏ½[31mοΏ½[1mDEPRECATED: οΏ½[0mοΏ½[33mοΏ½[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.οΏ½[0m


οΏ½[32mWe highly encourage switching to the new οΏ½[0mοΏ½[32mοΏ½[1m`scan`οΏ½[0mοΏ½[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.οΏ½[0m


οΏ½[33mοΏ½[1m+===========================================================================================================================================================================================+οΏ½[0m


+==============================================================================+

                               /$$$$$$            /$$
                              /$$__  $$          | $$
           /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$
          /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$
         |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$
          \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$
          /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$
         |_______/  \_______/|__/     \_______/   \___/   \____  $$
                                                          /$$  | $$
                                                         |  $$$$$$/
  by safetycli.com                                        \______/

+==============================================================================+

 οΏ½[1mREPORTοΏ½[0m 

  Safety οΏ½[1mv3.7.0οΏ½[0m is scanning for οΏ½[1mVulnerabilitiesοΏ½[0mοΏ½[1m...οΏ½[0m
οΏ½[1m  Scanning dependenciesοΏ½[0m in your οΏ½[1menvironment:οΏ½[0m

  -> /opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages

  Using οΏ½[1mopen-source vulnerability databaseοΏ½[0m
οΏ½[1m  Found and scanned 107 packagesοΏ½[0m
  Timestamp οΏ½[1m2026-05-01 09:27:19οΏ½[0m
οΏ½[1m  0οΏ½[0mοΏ½[1m vulnerabilities reportedοΏ½[0m
οΏ½[1m  0οΏ½[0mοΏ½[1m vulnerabilities ignoredοΏ½[0m
+==============================================================================+

 οΏ½[32mοΏ½[1mNo known security vulnerabilities reported.οΏ½[0m 

+==============================================================================+οΏ½[0m


οΏ½[33mοΏ½[1m+===========================================================================================================================================================================================+οΏ½[0m


οΏ½[31mοΏ½[1mDEPRECATED: οΏ½[0mοΏ½[33mοΏ½[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.οΏ½[0m


οΏ½[32mWe highly encourage switching to the new οΏ½[0mοΏ½[32mοΏ½[1m`scan`οΏ½[0mοΏ½[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.οΏ½[0m


οΏ½[33mοΏ½[1m+===========================================================================================================================================================================================+οΏ½[0m


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 inputs
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting aiohappyeyeballs (2.6.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing aiohappyeyeballs (2.6.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting aiohttp (3.13.5)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing aiohttp (3.13.5)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting aiosignal (1.4.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing aiosignal (1.4.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting annotated-doc (0.0.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing annotated-doc (0.0.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting annotated-types (0.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing annotated-types (0.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting anyio (4.13.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing anyio (4.13.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting attrs (26.1.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing attrs (26.1.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting Authlib (1.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing Authlib (1.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting bandit (1.9.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing bandit (1.9.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting black (26.3.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing black (26.3.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting boolean.py (5.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing boolean.py (5.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting build (1.5.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing build (1.5.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting CacheControl (0.14.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing CacheControl (0.14.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting certifi (2026.4.22)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing certifi (2026.4.22)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting cffi (2.0.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting cffi (2.0.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing cffi (2.0.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting charset-normalizer (3.4.7)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing charset-normalizer (3.4.7)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting click (8.3.3)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing click (8.3.3)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting coverage (7.13.5)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing coverage (7.13.5)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting cryptography (47.0.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing cryptography (47.0.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting cyclonedx-python-lib (11.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing cyclonedx-python-lib (11.7.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting defusedxml (0.7.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing defusedxml (0.7.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting docutils (0.22.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing docutils (0.22.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting dparse (0.6.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing dparse (0.6.4)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting filelock (3.29.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing filelock (3.29.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting frozenlist (1.8.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing frozenlist (1.8.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting h11 (0.16.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing h11 (0.16.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting httpcore (1.0.9)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing httpcore (1.0.9)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting httpx (0.28.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing httpx (0.28.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting id (1.6.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing id (1.6.1)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting idna (3.13)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing idna (3.13)
οΏ½[2KοΏ½[32m\οΏ½[0m Collecting iniconfig (2.3.0)
οΏ½[2KοΏ½[32m\οΏ½[0m Auditing iniconfig (2.3.0)
οΏ½[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.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing jaraco.functools (4.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting jeepney (0.9.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing jeepney (0.9.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting Jinja2 (3.1.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing Jinja2 (3.1.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting joblib (1.5.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing joblib (1.5.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting joserfc (1.6.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing joserfc (1.6.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting keyring (25.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing keyring (25.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting librt (0.9.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing librt (0.9.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.0.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing macversiontracker (1.0.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting markdown-it-py (4.0.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting markdown-it-py (4.0.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing markdown-it-py (4.0.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting MarkupSafe (3.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing MarkupSafe (3.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting marshmallow (4.3.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing marshmallow (4.3.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting mdurl (0.1.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing mdurl (0.1.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting more-itertools (11.0.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing more-itertools (11.0.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting msgpack (1.1.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing msgpack (1.1.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting multidict (6.7.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing multidict (6.7.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting mypy (1.20.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing mypy (1.20.2)
οΏ½[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.5)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing nh3 (0.3.5)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting nltk (3.9.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing nltk (3.9.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting packageurl-python (0.17.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing packageurl-python (0.17.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting packaging (26.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing packaging (26.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pathspec (1.1.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pathspec (1.1.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pip (26.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pip (26.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pip-api (0.0.34)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pip-api (0.0.34)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pip_audit (2.10.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pip_audit (2.10.0)
οΏ½[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.9.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing platformdirs (4.9.6)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pluggy (1.6.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pluggy (1.6.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting propcache (0.4.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing propcache (0.4.1)
οΏ½[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.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pycparser (3.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pydantic (2.13.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pydantic (2.13.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pydantic_core (2.46.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pydantic_core (2.46.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting Pygments (2.20.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing Pygments (2.20.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pyparsing (3.3.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pyparsing (3.3.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pyproject_hooks (1.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pyproject_hooks (1.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pyproject_hooks (1.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pytest (9.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pytest (9.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pytest-asyncio (1.3.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pytest-asyncio (1.3.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.15.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pytest-mock (3.15.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pytest-timeout (2.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pytest-timeout (2.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting pytokens (0.4.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing pytokens (0.4.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting PyYAML (6.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing PyYAML (6.0.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting readme_renderer (44.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing readme_renderer (44.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting regex (2026.4.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing regex (2026.4.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting requests (2.33.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing requests (2.33.1)
οΏ½[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 ruamel.yaml (0.19.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing ruamel.yaml (0.19.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting ruff (0.15.12)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing ruff (0.15.12)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting safety (3.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing safety (3.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting safety-schemas (0.0.16)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing safety-schemas (0.0.16)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting SecretStorage (3.5.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing SecretStorage (3.5.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting setuptools (82.0.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing setuptools (82.0.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting shellingham (1.5.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing shellingham (1.5.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting sortedcontainers (2.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing sortedcontainers (2.4.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting stevedore (5.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing stevedore (5.7.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tabulate (0.10.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tabulate (0.10.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tenacity (9.1.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tenacity (9.1.4)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tomli (2.4.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tomli (2.4.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tomli_w (1.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tomli_w (1.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tomlkit (0.14.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tomlkit (0.14.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting tqdm (4.67.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing tqdm (4.67.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting twine (6.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting twine (6.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing twine (6.2.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting typer (0.25.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing typer (0.25.1)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting types-PyYAML (6.0.12.20260408)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing types-PyYAML (6.0.12.20260408)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting typing_extensions (4.15.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing typing_extensions (4.15.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting typing-inspection (0.4.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing typing-inspection (0.4.2)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting urllib3 (2.6.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing urllib3 (2.6.3)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting wheel (0.47.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing wheel (0.47.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Collecting yarl (1.23.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing yarl (1.23.0)
οΏ½[2KοΏ½[32m|οΏ½[0m Auditing yarl (1.23.0)
οΏ½[?25h
οΏ½[1AοΏ½[2K```

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Catching Exception in run_versiontracker and only storing the exception object will hide the traceback context; consider capturing traceback.format_exc() or using exception.__traceback__ so the failure message includes full stack traces for easier debugging.
  • The .python-version bump is orthogonal to the thread-safety/test fix; consider moving that change into a separate PR or at least a separate commit to keep concerns isolated.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Catching `Exception` in `run_versiontracker` and only storing the exception object will hide the traceback context; consider capturing `traceback.format_exc()` or using `exception.__traceback__` so the failure message includes full stack traces for easier debugging.
- The `.python-version` bump is orthogonal to the thread-safety/test fix; consider moving that change into a separate PR or at least a separate commit to keep concerns isolated.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 1, 2026

Codecov Report

βœ… All modified and coverable lines are covered by tests.

πŸ“’ Thoughts on this report? Let us know!

@docdyhr docdyhr merged commit 572da62 into master May 1, 2026
31 checks passed
@docdyhr docdyhr deleted the fix/e2e-concurrent-test-thread-safe-argv branch May 1, 2026 09:34
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