You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "xFrone (via GitHub)" <gi...@apache.org> on 2026/07/25 14:33:59 UTC

[I] SSHRemoteJobOperator: the default Windows remote_base_dir is never expanded, so the job directory cannot be created [airflow]

xFrone opened a new issue, #70438:
URL: https://github.com/apache/airflow/issues/70438

   ### 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
   
   ```python
   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
   
   ```python
       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:
   
   ```python
   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:
   
   ```powershell
   $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
   
   ```powershell
   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?
   
   - [X] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] SSHRemoteJobOperator: the default Windows remote_base_dir is never expanded, so the job directory cannot be created [airflow]

Posted by "xFrone (via GitHub)" <gi...@apache.org>.
xFrone commented on issue #70438:
URL: https://github.com/apache/airflow/issues/70438#issuecomment-5078889232

   After looking at this more closely I think there is a third option that is smaller and
   less invasive than either of the two I listed above, and I would like to check the
   direction before opening a PR.
   
   Keep `$env:TEMP\airflow-ssh-jobs` as an opaque sentinel on the Python side, exactly as
   today, and expand it only at the point where a path is rendered into PowerShell. A
   single helper covers every site:
   
   ```python
   def _powershell_path(path: str) -> str:
       """Render a remote path as a PowerShell expression."""
       if path == WINDOWS_TEMP_PREFIX or path.startswith(WINDOWS_TEMP_PREFIX + "\\"):
           relative = path[len(WINDOWS_TEMP_PREFIX) :].lstrip("\\")
           if not relative:
               return "$env:TEMP"
           return f"(Join-Path $env:TEMP '{relative.replace(chr(39), chr(39) * 2)}')"
       return f"'{path.replace(chr(39), chr(39) * 2)}'"
   ```
   
   so `$jobDir = '{job_dir}'` becomes `$jobDir = {_powershell_path(job_dir)}` and emits
   
   ```powershell
   $jobDir = (Join-Path $env:TEMP 'airflow-ssh-jobs\af_dag_task_run_try1_abcdef')
   ```
   
   Why I prefer this:
   
   - **Every path stays literal data.** A user-supplied `remote_base_dir` never goes
     through expansion, so a `$` in it is still used verbatim. Only the exact default
     prefix is treated as an expression.
   - **No change to `_validate_job_dir`.** Both the job dir and the default base dir keep
     the sentinel on the Python side, so the existing prefix comparison is untouched.
   - **No change to the operator, the trigger, or the event payload.** The resolved path
     never has to travel back to Airflow, which the other two directions would have
     required — cleanup runs from `execute_complete` after deferral, where `_paths` is
     `None`, so a remotely-resolved base dir would have had to be threaded through the
     trigger event.
   - It is confined to the six `build_windows_*` builders in `remote_job.py`.
   
   Verified on Windows PowerShell 5.1: `Join-Path $env:TEMP 'a\b'` resolves correctly,
   is valid as a `-Path` argument, and `''` escaping handles a quote inside the relative
   part.
   
   One consequence worth calling out: user-visible logs and the trigger event would keep
   showing `$env:TEMP\airflow-ssh-jobs\...` rather than the resolved path, since Airflow
   still never learns the real location. I think that is acceptable — it is the status quo
   and it keeps the guard's comparison meaningful — but if you would rather Airflow know
   the real path, then resolving it during OS detection (option 1 above) is the way, at
   the cost of extending the trigger event.
   
   Happy to go with whichever you prefer. If there is no objection I will send a PR for
   this third option.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] SSHRemoteJobOperator: the default Windows remote_base_dir is never expanded, so the job directory cannot be created [airflow]

Posted by "boring-cyborg[bot] (via GitHub)" <gi...@apache.org>.
boring-cyborg[bot] commented on issue #70438:
URL: https://github.com/apache/airflow/issues/70438#issuecomment-5078869796

   Thanks for opening your first issue here! Be sure to follow the issue template! If you are willing to raise PR to address this issue please do so, no need to wait for approval.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org