fix(git-gate): make the gateway access-hook executable regardless of copy transport #403

Merged
didericis merged 1 commits from fix-git-gate-access-hook-exec-bit into main 2026-07-17 22:00:57 -04:00
Collaborator

Closes #404.

Summary

Cloning/fetching from the per-agent git-gate on the Apple-container backend failed with git clone reporting "empty reply from server" (curl exit 52). It looked like an auth/network problem but was neither — attribution and routing worked; the gateway's git-http handler was crashing on every upload-pack:

File "/app/git_http_backend.py", line 151, in _run_backend
    hook = subprocess.run([hook_path, "upload-pack", ...])
PermissionError: [Errno 13] Permission denied: '/etc/git-gate/access-hook'

The access-hook is exec'd directly, so it needs the x bit. prepare() stages it 0o700 and trusted the gateway copy to carry that mode. docker cp preserves source mode; the Apple container cp (AppleGatewayTransport.cp_into) does not, landing the hook 0o644EACCES. The unhandled exception killed the handler thread, closing the socket with no HTTP response — the opaque "empty reply."

Fix

  • provision_git_gate (backend-neutral): chmod +x the access-hook on the gateway side after the copy, so it's executable under every transport (docker / Apple / firecracker-ssh) rather than depending on the copy to preserve mode.
  • git-http handler (defense-in-depth): wrap the access-hook subprocess.run — an OSError/SubprocessError (un-execable, timed out) now fails closed with a 503 instead of crashing the thread into an empty reply. A gate that can't run its hook should deny, visibly.
  • Update the now-misleading "docker cp preserves source mode" comment in git_gate.prepare().

Tests

  • test_gateway_provision.py: provisioning applies chmod +x to the access-hook.
  • test_git_http_backend.py: the handler returns 503 (not an empty reply) when the hook can't be exec'd.

All 21 tests in the two affected files pass.

Note

The live "Claude Agent 1" bottle was unblocked out-of-band with container exec bot-bottle-mac-infra chmod +x /etc/git-gate/access-hook; this PR makes the fix durable across gateway restarts / re-provisions.

Closes #404. ## Summary Cloning/fetching from the per-agent git-gate on the **Apple-container backend** failed with `git clone` reporting "empty reply from server" (curl exit 52). It looked like an auth/network problem but was neither — attribution and routing worked; the gateway's git-http handler was **crashing on every upload-pack**: ``` File "/app/git_http_backend.py", line 151, in _run_backend hook = subprocess.run([hook_path, "upload-pack", ...]) PermissionError: [Errno 13] Permission denied: '/etc/git-gate/access-hook' ``` The access-hook is exec'd directly, so it needs the x bit. `prepare()` stages it `0o700` and trusted the gateway copy to carry that mode. `docker cp` preserves source mode; the Apple `container cp` (`AppleGatewayTransport.cp_into`) does **not**, landing the hook `0o644` → `EACCES`. The unhandled exception killed the handler thread, closing the socket with no HTTP response — the opaque "empty reply." ## Fix - **`provision_git_gate`** (backend-neutral): `chmod +x` the access-hook on the gateway side after the copy, so it's executable under every transport (docker / Apple / firecracker-ssh) rather than depending on the copy to preserve mode. - **git-http handler** (defense-in-depth): wrap the access-hook `subprocess.run` — an `OSError`/`SubprocessError` (un-execable, timed out) now fails closed with a **503** instead of crashing the thread into an empty reply. A gate that can't run its hook should deny, visibly. - Update the now-misleading "docker cp preserves source mode" comment in `git_gate.prepare()`. ## Tests - `test_gateway_provision.py`: provisioning applies `chmod +x` to the access-hook. - `test_git_http_backend.py`: the handler returns 503 (not an empty reply) when the hook can't be exec'd. All 21 tests in the two affected files pass. ## Note The live "Claude Agent 1" bottle was unblocked out-of-band with `container exec bot-bottle-mac-infra chmod +x /etc/git-gate/access-hook`; this PR makes the fix durable across gateway restarts / re-provisions.
didericis-claude added 1 commit 2026-07-17 21:49:59 -04:00
fix(git-gate): make the gateway access-hook executable regardless of copy transport
test / unit (pull_request) Successful in 1m19s
test / integration (pull_request) Successful in 30s
test / coverage (pull_request) Successful in 1m35s
lint / lint (push) Successful in 2m35s
test / unit (push) Successful in 1m21s
test / integration (push) Successful in 29s
test / coverage (push) Successful in 1m31s
Update Quality Badges / update-badges (push) Successful in 1m21s
b1850be5d1
Cloning/fetching from the git-gate on the Apple-container backend failed with
"empty reply from server" (curl exit 52). Root cause: the git-http handler
crashed on every upload-pack with

    PermissionError: [Errno 13] Permission denied: '/etc/git-gate/access-hook'

The access-hook is exec'd directly, so it needs the x bit. prepare() stages it
0o700 and trusted the gateway copy to carry that mode. `docker cp` does; the
Apple `container cp` (AppleGatewayTransport) does not, landing the hook 0o644 →
EACCES. The unhandled exception killed the handler thread, closing the socket
with no HTTP response — which the client sees as the opaque empty reply.

- provision_git_gate now `chmod +x`es the access-hook on the gateway side after
  the copy, so it's executable under every transport (docker/apple/firecracker).
- git-http handler wraps the access-hook subprocess.run: an OSError /
  SubprocessError (un-execable, timed out) now fails closed with a 503 instead
  of crashing the thread into an empty reply — a gate that can't run its hook
  should deny, visibly.
- Updates the now-misleading "docker cp preserves source mode" comment in
  git_gate.prepare().

Regression tests: provisioning applies +x to the access-hook; the handler
returns 503 (not an empty reply) when the hook can't be exec'd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
didericis-claude added the Kind/Bug label 2026-07-17 21:54:27 -04:00
didericis-claude removed the Kind/Bug label 2026-07-17 21:59:42 -04:00
didericis approved these changes 2026-07-17 22:00:43 -04:00
didericis merged commit b1850be5d1 into main 2026-07-17 22:00:57 -04:00
didericis deleted branch fix-git-gate-access-hook-exec-bit 2026-07-17 22:00:58 -04:00
Sign in to join this conversation.