Skip to content

GitDagBundle GitHub App auth fails on Linux: askpass script exec'd while still open for writing (ETXTBSY) #73425

Closed

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
    ...
    yield

The 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 hello

End 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

Activity

  1. boring-cyborg commented on Sep 21, 2026

    @boring-cyborg

    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.

  2. MichalJaroslawKrzywanski-TomTom commented on Sep 22, 2026

    @MichalJaroslawKrzywanski-TomTom
    ContributorAuthor

    Fixed on main by #64105, so closing this.

    That PR removed the GIT_ASKPASS mechanism 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, so cannot exec '/tmp/tmpXXXX.sh': Text file busy can no longer occur for token or GitHub App authentication. I ran the provider hook tests against main in apache/airflow:3.2.2-python3.10 (git 2.39.5) and all 55 pass, including the ones that invoke real git ls-remote calls through the new helper.

    Two notes for anyone who lands here from a search:

    • The fix is not released yet. apache-airflow-providers-git 0.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-wide credential.helper for the repository host.
    • The same open-file-then-exec pattern is still present in _passphrase_askpass_env, the SSH_ASKPASS helper 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions