Skip to content

SSHRemoteJobOperator: the default Windows remote_base_dir is never expanded, so the job directory cannot be created #70438

Description

@xFrone

Under which category would you file this issue?

Providers

Apache Airflow version

main (cd7235aff3), apache-airflow-providers-ssh 6.0.0

What happened and how to reproduce it?

SSHRemoteJobOperator cannot run against a Windows remote when remote_base_dir is
left at its default, because every generated PowerShell command embeds the default
base directory in a single-quoted string, where PowerShell does not expand
$env:TEMP.

WINDOWS_DEFAULT_BASE_DIR is defined as a PowerShell expression:

https://github.com/apache/airflow/blob/main/providers/ssh/src/airflow/providers/ssh/utils/remote_job.py#L30

WINDOWS_DEFAULT_BASE_DIR = "$env:TEMP\\airflow-ssh-jobs"

but build_windows_wrapper_command interpolates the resulting path into a
single-quoted assignment:

https://github.com/apache/airflow/blob/main/providers/ssh/src/airflow/providers/ssh/utils/remote_job.py#L289-L290

    wrapper = f"""$jobDir = '{job_dir}'
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null

Single quotes are literal in PowerShell, so $jobDir holds the literal text
$env:TEMP\airflow-ssh-jobs\.... PowerShell then parses the leading $env: as a
PSDrive qualifier and fails.

Reproduce without a remote host — decode what the provider generates:

from airflow.providers.ssh.utils.remote_job import RemoteJobPaths, build_windows_wrapper_command
import base64

paths = RemoteJobPaths(job_id="af_dag_task_run_try1_abcdef", remote_os="windows")
cmd = build_windows_wrapper_command(command="echo hello", paths=paths)
print(base64.b64decode(cmd.split("-EncodedCommand ")[1]).decode("utf-16-le"))

Output:

$jobDir = '$env:TEMP\airflow-ssh-jobs\af_dag_task_run_try1_abcdef'
New-Item -ItemType Directory -Force -Path $jobDir | Out-Null
$log = '$env:TEMP\airflow-ssh-jobs\af_dag_task_run_try1_abcdef\stdout.log'
'' | Set-Content -Path $log
...

Running those two lines in Windows PowerShell 5.1 (Windows 11):

New-Item : Cannot find drive. A drive with the name '$env' does not exist.
    + CategoryInfo          : ObjectNotFound: ($env:String) [New-Item], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.NewItemCommand

Nothing is created — neither a literal $env:TEMP folder nor anything under the real
%TEMP%. Set-Content -Path $log fails identically, so the job directory and log file
never exist and the job cannot start.

Cleanup is affected too, but silently: build_windows_cleanup_command produces

Remove-Item -Recurse -Force -Path '$env:TEMP\airflow-ssh-jobs\...' -ErrorAction SilentlyContinue

which suppresses the same DriveNotFoundException and removes nothing.

Setting remote_base_dir to a literal path (for example C:\Temp\airflow-ssh-jobs)
avoids all of this, which is presumably why the default has not been hit before.

What you think should happen instead?

The default should resolve to the remote user's temporary directory.

The paths are used as literal data in single-quoted PowerShell strings, so the
expansion cannot happen at string-build time on the Airflow side — the value is only
known on the remote host. Two directions:

  1. Resolve the temporary directory on the remote as part of the existing OS detection
    step (build_windows_os_detection_command already runs a probe) and use the
    returned literal path as the default base dir.
  2. Or emit the assignment double-quoted ($jobDir = "...") so PowerShell expands it,
    and quote the remainder appropriately. This is more invasive, since every path is
    currently treated as literal data and a user-supplied remote_base_dir containing
    $ would then be interpreted rather than used verbatim.

Direction 1 looks safer to me because it keeps every emitted path literal and confines
the change to how the default is obtained.

Operating System

Remote: Windows. Verified the PowerShell behaviour on Windows 11 with Windows PowerShell 5.1.

Deployment

Other

Deployment details

Not deployment specific — reproducible from the provider's command builders alone.

Anything else?

Introduced with the operator in #60297. Occurs on every run against a Windows remote
that does not set remote_base_dir.

I could not find an existing issue or open PR covering this. The open PRs touching
WINDOWS_DEFAULT_BASE_DIR (#69834, #69885, #70098, #70233) all address #69813, which
is a different problem — that the cleanup guard compares against the default base dir
rather than the configured one.

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions