Skip to content

Enable ruff ASYNC rules - #7441

Open
danielhollas wants to merge 2 commits into
aiidateam:mainfrom
danielhollas:ruff-async
Open

Enable ruff ASYNC rules#7441
danielhollas wants to merge 2 commits into
aiidateam:mainfrom
danielhollas:ruff-async

Conversation

@danielhollas

@danielhollas danielhollas commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

What it says on the tin, we've got a lot of async code so let's enable linter rules for it!

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4fda74d9-69af-47ae-a63b-0d7f3b2164ef

📥 Commits

Reviewing files that changed from the base of the PR and between d9795c1 and 58b6adb.

📒 Files selected for processing (2)
  • pyproject.toml
  • src/aiida/transports/plugins/ssh_async.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/aiida/transports/plugins/ssh_async.py
  • pyproject.toml

📝 Walkthrough

Walkthrough

Ruff async checks are enabled. Targeted suppressions are added to existing polling loops and an asynchronous SSH subprocess call. Runtime and test control flow remain unchanged.

Changes

Async lint configuration and suppressions

Layer / File(s) Summary
Enable async Ruff rules
pyproject.toml
The ASYNC rule group is selected. ASYNC240 and ASYNC230 are ignored.
Suppress existing async findings
src/aiida/engine/processes/futures.py, src/aiida/transports/plugins/ssh_async.py, tests/engine/test_*.py
Inline ASYNC110 and ASYNC221 suppressions are added to existing polling loops and the SSH subprocess call. Behavior is unchanged.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: agoscinski, geigerj2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes enabling Ruff ASYNC lint rules, which is the main change.
Description check ✅ Passed The description directly explains that Ruff ASYNC lint rules are enabled for the repository's async code.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.67%. Comparing base (299e945) to head (58b6adb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7441      +/-   ##
==========================================
- Coverage   80.68%   80.67%   -0.00%     
==========================================
  Files         581      581              
  Lines       47068    47068              
==========================================
- Hits        37972    37968       -4     
- Misses       9096     9100       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.


if self.auth_script != 'None':
result = subprocess.run(self.auth_script, shell=True, capture_output=True, text=True, check=False)
result = subprocess.run(self.auth_script, shell=True, capture_output=True, text=True, check=False) # noqa: ASYNC221

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This looks like a real issue that should be fixed. Quoting from ruff:


ASYNC221

Checks that async functions do not run processes with blocking methods.

Why is this bad?

Blocking an async function via a blocking call will block the entire
event loop, preventing it from executing other tasks while waiting for the
call to complete, negating the benefits of asynchronous programming.

Instead of making a blocking call, use an equivalent asynchronous library or function, like trio.run_process() or anyio.run_process().

Example

import subprocess


async def foo():
    subprocess.run(cmd)

Use instead:

import asyncio


async def foo():
    asyncio.create_subprocess_shell(cmd)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in general to asynchronize this function is good direction for further performance but I don't think it is safe to suspend within this open coroutine without any further changes. In this code we are already pass the if self._is_open: check, so the next coroutine could be another open and open another connection. So we would need a variable expressing that a connection is in the progress of being opened that need to be checked. In 2.10 we anyway wanted to make more functions async, so I think about it after release.

Comment thread pyproject.toml

[tool.ruff.lint]
ignore = [
'ASYNC240', # blocking-path-method-in-async-function

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

What it does

Checks that async functions do not call blocking os.path or pathlib.Path
methods.

Why is this bad?

Calling some os.path or pathlib.Path methods in an async function will block
the entire event loop, preventing it from executing other tasks while waiting
for the operation. This negates the benefits of asynchronous programming.

Instead, use the methods' async equivalents from trio.Path or anyio.Path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In order to resolve ASYNC230 and ASYNC240 we would need to take a dependency on a third party library that enables async file IO.

async def do_pause():
calc_node = self.runner.submit(test_processes.WaitProcess)
while calc_node.process_state != ProcessState.WAITING:
while calc_node.process_state != ProcessState.WAITING: # noqa: ASYNC110

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I suspect that in order to avoid sleeping these tests could be refactored to use the plumpy.process_listener.ProcessListener callback based solution? But I haven't gone deep enough to see if that is viable here.

https://plumpy.readthedocs.io/en/latest/apidoc/plumpy.process_listener.html

@danielhollas
danielhollas requested a review from agoscinski July 20, 2026 16:58
@danielhollas
danielhollas marked this pull request as ready for review July 20, 2026 16:58
@danielhollas
danielhollas requested a review from GeigerJ2 as a code owner July 20, 2026 16:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiida/transports/plugins/ssh_async.py`:
- Line 171: Update the subprocess invocation in the authentication flow to pass
auth_script as a single executable argument rather than using shell=True.
Preserve the existing capture_output, text, and check behavior, and rely on
validate_script’s path-only validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f37815f-219e-429c-a747-72cd4389d95c

📥 Commits

Reviewing files that changed from the base of the PR and between 9bffcdb and d9795c1.

📒 Files selected for processing (6)
  • pyproject.toml
  • src/aiida/engine/processes/futures.py
  • src/aiida/transports/plugins/ssh_async.py
  • tests/engine/test_daemon.py
  • tests/engine/test_rmq.py
  • tests/engine/test_zeromq.py


if self.auth_script != 'None':
result = subprocess.run(self.auth_script, shell=True, capture_output=True, text=True, check=False)
result = subprocess.run(self.auth_script, shell=True, capture_output=True, text=True, check=False) # noqa: ASYNC221

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A10 -B10 'def validate_script|authentication_script|auth_script' src tests

Repository: aiidateam/aiida-core

Length of output: 12873


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== surrounding code =="
sed -n '1,220p' src/aiida/transports/plugins/ssh_async.py

echo
echo "== similar subprocess usage =="
rg -n "subprocess\.run\(" src/aiida/transports -g '*.py'

Repository: aiidateam/aiida-core

Length of output: 9775


Avoid shell=True for authentication_script.
validate_script only checks that this is an absolute, executable path; it still passes the raw string to a shell, so a path containing shell metacharacters can run extra commands. Use subprocess.run([self.auth_script], ...) if this option is path-only.

🧰 Tools
🪛 OpenGrep (1.25.0)

[ERROR] 171-171: Dynamic command passed to subprocess with shell=True. Use a command list without shell=True, or use shlex.quote() to sanitize input.

(coderabbit.command-injection.python-shell-true)

🪛 Ruff (0.15.21)

[error] 171-171: subprocess call with shell=True identified, security issue

(S602)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiida/transports/plugins/ssh_async.py` at line 171, Update the subprocess
invocation in the authentication flow to pass auth_script as a single executable
argument rather than using shell=True. Preserve the existing capture_output,
text, and check behavior, and rely on validate_script’s path-only validation.

Source: Linters/SAST tools

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

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants