Skip to content

fix(ssh): add optional remote_root auto-creation#590

Open
njzjz-bot wants to merge 1 commit intodeepmodeling:masterfrom
njzjz-bot:fix/issue-436-create-remote-root
Open

fix(ssh): add optional remote_root auto-creation#590
njzjz-bot wants to merge 1 commit intodeepmodeling:masterfrom
njzjz-bot:fix/issue-436-create-remote-root

Conversation

@njzjz-bot
Copy link
Copy Markdown
Contributor

@njzjz-bot njzjz-bot commented Mar 29, 2026

Problem

  • SSHContext currently only attempts a single mkdir for remote_root, so configurations fail when parent directories do not already exist.
  • Some users want DPDispatcher to create the remote working root for them, but doing that unconditionally would make path typos easier to miss.

Change

  • Add an opt-in create_remote_root boolean for SSHContext; when enabled, DPDispatcher recursively creates remote_root and the submission working directory.
  • Keep the default behavior unchanged (false) so mistyped remote paths still fail loudly.
  • Persist the new option through Machine.serialize(), add unit tests, and document it in the SSH docs/examples.

Tests

  • ./.tmp-test-venv/bin/python -m pytest tests/test_ssh_create_remote_root.py tests/test_argcheck.py -q

Closes #436

Authored by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.4)

Summary by CodeRabbit

  • New Features

    • SSH machines now support automatic creation of remote directory trees via a new optional configuration parameter, eliminating the need to manually pre-create remote directories.
  • Documentation

    • Updated configuration guides and example files to document the new remote directory creation option.
  • Tests

    • Added test coverage for remote directory creation functionality.

Add a create_remote_root switch for SSHContext so DPDispatcher can
recursively create the configured remote_root when users opt in.
This preserves the current safe default while fixing setups where
parent directories do not already exist.

Also persist the new option through Machine.serialize(), add unit
tests for recursive mkdir and config round-tripping, and document the
new knob in the SSH examples.

Authored by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.4)
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Mar 29, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 29, 2026

Codecov Report

❌ Patch coverage is 82.75862% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.85%. Comparing base (eda0633) to head (8eb2596).

Files with missing lines Patch % Lines
dpdispatcher/contexts/ssh_context.py 80.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #590      +/-   ##
==========================================
- Coverage   49.59%   48.85%   -0.74%     
==========================================
  Files          40       40              
  Lines        3958     3977      +19     
==========================================
- Hits         1963     1943      -20     
- Misses       1995     2034      +39     

☔ View full report in Codecov by Sentry.
📢 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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR introduces a create_remote_root configuration option to SSHContext that automatically creates remote directory trees when enabled. A new _mkdir helper method handles recursive directory creation via SFTP. Documentation, examples, serialization logic, and tests are updated to support the new feature.

Changes

Cohort / File(s) Summary
Documentation
doc/context.md, doc/getting-started.md
Updated documentation to explain create_remote_root parameter and its default behavior; added example configuration with the new field.
Core Implementation
dpdispatcher/contexts/ssh_context.py
Added create_remote_root boolean parameter to SSHContext, introduced _mkdir helper method for recursive directory creation via SFTP, replaced direct sftp.mkdir calls with conditional recursive creation, and exposed create_remote_root as a machine subfield argument.
Serialization
dpdispatcher/machine.py
Updated serialize() method to conditionally include create_remote_root and clean_asynchronously fields in output when present on the context object.
Examples
examples/machine/ssh_proxy_command.json
Added create_remote_root: true field to example SSH context configuration.
Tests
tests/test_argcheck.py, tests/test_ssh_create_remote_root.py
Added comprehensive unit tests validating SSH machine argument handling, _mkdir recursive behavior, and configuration persistence through serialization/deserialization cycles.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

lgtm

Suggested reviewers

  • njzjz
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an optional remote_root auto-creation feature to SSH context.
Linked Issues check ✅ Passed All coding requirements from issue #436 are met: SSHContext now has create_remote_root parameter that recursively creates missing parent directories when enabled, preserving default safe behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the create_remote_root feature for issue #436; no unrelated or out-of-scope modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
dpdispatcher/contexts/ssh_context.py (2)

545-565: Add type hints to _mkdir method and consider logging suppressed errors.

Two observations:

  1. Missing type hints - Per coding guidelines, add type annotations:

  2. Silent error suppression - The OSError catch (lines 553-554, 564-565) will silently ignore not just "directory exists" but also permission errors, disk full, etc. Consider logging at debug level when an exception occurs, or checking errno.EEXIST specifically.

Suggested type hints
-    def _mkdir(self, remote_dir, recursive=False):
+    def _mkdir(self, remote_dir: str, recursive: bool = False) -> None:
         if not remote_dir:
             return
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpdispatcher/contexts/ssh_context.py` around lines 545 - 565, Add proper type
annotations to the _mkdir method signature (e.g., remote_dir: str | None,
recursive: bool) and return type (-> None) and update any internal variable
hints if needed; then replace the broad silent OSError suppression in the
non-recursive and recursive mkdir blocks (inside _mkdir, referencing self.sftp
and pathlib.PurePosixPath) with targeted handling: check the exception errno
(import errno) and only ignore errno.EEXIST, otherwise log the exception at
debug or warn level via the module/class logger (do not raise for unexpected
errors) so permission/disk errors are visible during debugging.

467-467: Add type hint for create_remote_root parameter.

Per coding guidelines, Python code should include type annotations for better maintainability.

Suggested type hint
     def __init__(
         self,
         local_root,
         remote_root,
         remote_profile,
         clean_asynchronously=False,
-        create_remote_root=False,
+        create_remote_root: bool = False,
         *args,
         **kwargs,
     ):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dpdispatcher/contexts/ssh_context.py` at line 467, Add a type annotation to
the create_remote_root parameter (e.g., change it to create_remote_root: bool =
False) where it appears so the function signature includes the type; ensure any
needed typing imports are present and update the docstring/signature in the same
function to reflect the boolean type if applicable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dpdispatcher/machine.py`:
- Around line 178-181: serialize() currently only adds clean_asynchronously when
the context object has that attribute, which omits the key for LocalContext and
breaks test_machine_argcheck; update serialize(self, if_empty_remote_profile:
bool = False) -> dict to always include "clean_asynchronously" (use
getattr(self.context, "clean_asynchronously", False) or similar) and also
include create_remote_root consistently (use getattr with a sensible default),
and add the requested type annotation to the serialize method signature so it
returns a dict with the optional if_empty_remote_profile boolean parameter; this
keeps tests expecting "clean_asynchronously": False working and satisfies the
typing guideline.

---

Nitpick comments:
In `@dpdispatcher/contexts/ssh_context.py`:
- Around line 545-565: Add proper type annotations to the _mkdir method
signature (e.g., remote_dir: str | None, recursive: bool) and return type (->
None) and update any internal variable hints if needed; then replace the broad
silent OSError suppression in the non-recursive and recursive mkdir blocks
(inside _mkdir, referencing self.sftp and pathlib.PurePosixPath) with targeted
handling: check the exception errno (import errno) and only ignore errno.EEXIST,
otherwise log the exception at debug or warn level via the module/class logger
(do not raise for unexpected errors) so permission/disk errors are visible
during debugging.
- Line 467: Add a type annotation to the create_remote_root parameter (e.g.,
change it to create_remote_root: bool = False) where it appears so the function
signature includes the type; ensure any needed typing imports are present and
update the docstring/signature in the same function to reflect the boolean type
if applicable.
🪄 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

Run ID: 18987df2-4b74-4ec1-ba65-d4134a5952ae

📥 Commits

Reviewing files that changed from the base of the PR and between eda0633 and 8eb2596.

📒 Files selected for processing (7)
  • doc/context.md
  • doc/getting-started.md
  • dpdispatcher/contexts/ssh_context.py
  • dpdispatcher/machine.py
  • examples/machine/ssh_proxy_command.json
  • tests/test_argcheck.py
  • tests/test_ssh_create_remote_root.py

Comment on lines +178 to +181
if hasattr(self.context, "clean_asynchronously"):
machine_dict["clean_asynchronously"] = self.context.clean_asynchronously
if hasattr(self.context, "create_remote_root"):
machine_dict["create_remote_root"] = self.context.create_remote_root
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if LocalContext defines clean_asynchronously attribute
rg -n "clean_asynchronously" dpdispatcher/contexts/local_context.py

# Check if the test passes with current implementation
rg -nA5 "def test_machine_argcheck" tests/test_argcheck.py

Repository: deepmodeling/dpdispatcher

Length of output: 297


🏁 Script executed:

#!/bin/bash
# Get the full test_machine_argcheck test
sed -n '12,35p' tests/test_argcheck.py

Repository: deepmodeling/dpdispatcher

Length of output: 838


🏁 Script executed:

#!/bin/bash
# Get the serialize method in machine.py around lines 178-181 and show more context
sed -n '160,200p' dpdispatcher/machine.py

Repository: deepmodeling/dpdispatcher

Length of output: 1824


Serialization issue: LocalContext machines will not include clean_asynchronously in serialized output, but the test expects it.

The serialize() method conditionally includes clean_asynchronously only via hasattr(self.context, "clean_asynchronously"). However, LocalContext does not define this attribute, so for LocalContext machines the field will not be included in the serialized dict. Yet test_machine_argcheck expects "clean_asynchronously": False in the output for a LocalContext machine (line 29). This will cause the test to fail.

Either:

  1. LocalContext needs to define clean_asynchronously as an attribute, or
  2. The test expectation is incorrect

Additionally, the serialize() method lacks type hints. Per the coding guidelines, add proper type annotations: def serialize(self, if_empty_remote_profile: bool = False) -> dict:

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

In `@dpdispatcher/machine.py` around lines 178 - 181, serialize() currently only
adds clean_asynchronously when the context object has that attribute, which
omits the key for LocalContext and breaks test_machine_argcheck; update
serialize(self, if_empty_remote_profile: bool = False) -> dict to always include
"clean_asynchronously" (use getattr(self.context, "clean_asynchronously", False)
or similar) and also include create_remote_root consistently (use getattr with a
sensible default), and add the requested type annotation to the serialize method
signature so it returns a dict with the optional if_empty_remote_profile boolean
parameter; this keeps tests expecting "clean_asynchronously": False working and
satisfies the typing guideline.

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

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fail to run task if remote_root path doesn't existed

1 participant