Repository navigation
GitDagBundle GitHub App auth fails on Linux: askpass script exec'd while still open for writing (ETXTBSY) #73425
Description
Apache Airflow Provider(s)
git
Versions of Apache Airflow Providers
apache-airflow-providers-git==0.5.0
Apache Airflow version
3.2.2
Operating System
Debian GNU/Linux 12 (bookworm) — apache/airflow:3.2.2-python3.10 image
Deployment
Official Apache Airflow Helm Chart
Deployment details
Chart 1.22.0, KubernetesExecutor. GitDagBundle configured with a git connection using GitHub App auth (github_app_id, github_installation_id, key_file in extras, HTTPS host, no password).
What happened
Every GitDagBundle clone/fetch that uses GitHub App authentication fails on Linux:
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
cmdline: git clone -v --bare -- https://github.com/<org>/<repo>.git /tmp/airflow/dag_bundles/<bundle>/bare
stderr: 'Cloning into bare repository '/tmp/airflow/dag_bundles/<bundle>/bare'...
fatal: cannot exec '/tmp/tmp64wabz1p.sh': Text file busy
fatal: could not read Username for 'https://github.com': terminal prompts disabled
'
The token exchange itself succeeds (Successfully obtained GitHub App installation access token is logged). The failure is in GitHook._github_app_askpass_env (hooks/git.py, lines 246–294 in 0.5.0):
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=True) as askpass_script:
askpass_script.write(...)
askpass_script.flush()
os.chmod(askpass_script.name, stat.S_IRWXU)
...
os.environ["GIT_ASKPASS"] = askpass_script.name
...
yieldThe file object stays open for writing for the whole yield. Linux refuses to execve a file that has a writer open (ETXTBSY), so git cannot run the askpass helper, falls back to prompting, and fails because GIT_TERMINAL_PROMPT=0. For GitHub App auth the askpass helper is the only credential path (the token is not spliced into the URL, since _process_git_auth_url runs in __init__ before the token exists), so App auth cannot work on Linux at all. macOS does not enforce ETXTBSY, which is presumably why this passed local testing.
_passphrase_askpass_env (lines 317–350) has the same pattern for SSH_ASKPASS and should fail the same way when a passphrase is set.
What you think should happen instead
The askpass script should be closed before git is invoked. E.g. tempfile.mkstemp(), write, os.close(), chmod, and os.unlink() in finally (Python 3.12's delete_on_close=False would also do, but the provider supports 3.10). #64105 replaces this mechanism with a credential helper that takes the token from the environment; if that lands first it fixes this as a side effect.
How to reproduce
Minimal reproduction of the exec failure, run inside apache/airflow:3.2.2-python3.10:
import tempfile, os, stat, subprocess
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=True) as f:
f.write("#!/bin/sh\necho hello\n"); f.flush(); os.chmod(f.name, stat.S_IRWXU)
subprocess.run([f.name]) # OSError: [Errno 26] Text file busy
f = tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False)
f.write("#!/bin/sh\necho hello\n"); f.close(); os.chmod(f.name, stat.S_IRWXU)
subprocess.run([f.name]) # prints helloEnd to end: configure a GitDagBundle against a private GitHub repo with a git connection carrying github_app_id, github_installation_id and key_file extras on Linux; the first bare clone fails as above.
Anything else
Reproduced with git 2.39.5 (the version in the official image) and GitPython 3.1.x. Happens on every attempt, in both the DAG processor and KubernetesExecutor task pods.
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Code of Conduct
- I agree to follow this project's Code of Conduct
Activity
boring-cyborg commentedon Sep 21, 2026
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.
fix(git): close the ssh askpass helper before ssh executes it
MichalJaroslawKrzywanski-TomTom commentedon Sep 22, 2026
Fixed on
mainby #64105, so closing this.That PR removed the
GIT_ASKPASSmechanism this issue reported on and replaced it with a host-scoped credential helper that is written and closed before git runs, which is exactly the constraint the report was about. The failing exec path no longer exists, socannot exec '/tmp/tmpXXXX.sh': Text file busycan no longer occur for token or GitHub App authentication. I ran the provider hook tests againstmaininapache/airflow:3.2.2-python3.10(git 2.39.5) and all 55 pass, including the ones that invoke realgit ls-remotecalls through the new helper.Two notes for anyone who lands here from a search:
apache-airflow-providers-git0.5.0, currently the newest on PyPI, still has the bug, so on Linux a workaround is needed until the next provider release. Prevent cleartext credential storage in Git Dag bundles #64105 also requires git 2.31 or newer on the Dag processor and workers, and it resets any deployment-widecredential.helperfor the repository host._passphrase_askpass_env, theSSH_ASKPASShelper used to unlock a passphrase-protected private key. That is not what this issue reported, and it is fixed separately by Close the ssh askpass helper in GitHook before ssh executes it #73427.Close the ssh askpass helper in GitHook before ssh executes it (#73427)