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:
- 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.
- 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?
Code of Conduct
Under which category would you file this issue?
Providers
Apache Airflow version
main (
cd7235aff3),apache-airflow-providers-ssh6.0.0What happened and how to reproduce it?
SSHRemoteJobOperatorcannot run against a Windows remote whenremote_base_dirisleft 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_DIRis defined as a PowerShell expression:https://github.com/apache/airflow/blob/main/providers/ssh/src/airflow/providers/ssh/utils/remote_job.py#L30
but
build_windows_wrapper_commandinterpolates the resulting path into asingle-quoted assignment:
https://github.com/apache/airflow/blob/main/providers/ssh/src/airflow/providers/ssh/utils/remote_job.py#L289-L290
Single quotes are literal in PowerShell, so
$jobDirholds the literal text$env:TEMP\airflow-ssh-jobs\.... PowerShell then parses the leading$env:as aPSDrive qualifier and fails.
Reproduce without a remote host — decode what the provider generates:
Output:
Running those two lines in Windows PowerShell 5.1 (Windows 11):
Nothing is created — neither a literal
$env:TEMPfolder nor anything under the real%TEMP%.Set-Content -Path $logfails identically, so the job directory and log filenever exist and the job cannot start.
Cleanup is affected too, but silently:
build_windows_cleanup_commandproduceswhich suppresses the same
DriveNotFoundExceptionand removes nothing.Setting
remote_base_dirto a literal path (for exampleC:\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:
step (
build_windows_os_detection_commandalready runs a probe) and use thereturned literal path as the default base dir.
$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_dircontaining$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, whichis a different problem — that the cleanup guard compares against the default base dir
rather than the configured one.
Are you willing to submit PR?
Code of Conduct