Skip to content

Commit c9c19da

Browse files
committed
fix(tasks): deliver the per-actor github token to gh via a PATH shim
The backend delivers the per-actor GitHub token through BASH_ENV, which only non-interactive `bash -c` honors. The agent runs its tool commands in an interactive shell, so `gh` there had no token and PR creation failed once the frozen process-env token was removed. Add a gh PATH shim (mirroring git-guard) installed first on PATH that sources the same credential script on every call, so gh authenticates as the current actor regardless of shell mode and honors logout (an emptied file exports nothing). gh is the only env-dependent consumer: git uses the remote URL and the signed-commit tool reads the file in-process.
1 parent 52573b0 commit c9c19da

4 files changed

Lines changed: 47 additions & 2 deletions

File tree

products/tasks/backend/logic/services/modal_sandbox.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def _resource_create_kwargs(config: SandboxConfig) -> dict[str, object]:
185185
}
186186
LOCAL_MODAL_INSTALL_SKILLS_SCRIPT = Path("products/tasks/backend/sandbox/images/install-skills.sh")
187187
LOCAL_MODAL_GIT_GUARD_SCRIPT = Path("products/tasks/backend/sandbox/images/git-guard.sh")
188+
LOCAL_MODAL_GH_GUARD_SCRIPT = Path("products/tasks/backend/sandbox/images/gh-guard.sh")
188189

189190

190191
_image_ref_cache: TTLCache = TTLCache(maxsize=3, ttl=300)
@@ -445,11 +446,14 @@ def _prepare_local_modal_build_context(template: SandboxTemplate) -> tuple[str,
445446
destination_dockerfile_path.parent.mkdir(parents=True, exist_ok=True)
446447
shutil.copy2(source_dockerfile_path, destination_dockerfile_path)
447448

448-
# Both base and notebook Dockerfiles COPY the git guard, so include it in
449-
# every local build context.
449+
# Both base and notebook Dockerfiles COPY the git and gh guards, so include
450+
# them in every local build context.
450451
destination_git_guard_path = context_dir / LOCAL_MODAL_GIT_GUARD_SCRIPT
451452
destination_git_guard_path.parent.mkdir(parents=True, exist_ok=True)
452453
shutil.copy2(base_dir / LOCAL_MODAL_GIT_GUARD_SCRIPT, destination_git_guard_path)
454+
destination_gh_guard_path = context_dir / LOCAL_MODAL_GH_GUARD_SCRIPT
455+
destination_gh_guard_path.parent.mkdir(parents=True, exist_ok=True)
456+
shutil.copy2(base_dir / LOCAL_MODAL_GH_GUARD_SCRIPT, destination_gh_guard_path)
453457

454458
if template == SandboxTemplate.DEFAULT_BASE:
455459
source_install_script_path = base_dir / LOCAL_MODAL_INSTALL_SKILLS_SCRIPT

products/tasks/backend/sandbox/images/Dockerfile.sandbox-base

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ RUN git config --global user.email "code@posthog.com" && \
158158
# Block `git commit`/`git push` so unsigned commits cannot leave the sandbox
159159
COPY products/tasks/backend/sandbox/images/git-guard.sh /opt/posthog/bin/git
160160
RUN chmod +x /opt/posthog/bin/git
161+
# Inject the per-actor GitHub token into every `gh` call (BASH_ENV only reaches
162+
# non-interactive `bash -c`; the agent's interactive shell needs this shim).
163+
COPY products/tasks/backend/sandbox/images/gh-guard.sh /opt/posthog/bin/gh
164+
RUN chmod +x /opt/posthog/bin/gh
161165
ENV PATH="/opt/posthog/bin:${PATH}"
162166

163167
# This is required for the Claude Code SDK to allow --dangerously-skip-permissions as the root user

products/tasks/backend/sandbox/images/Dockerfile.sandbox-notebook

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ RUN git config --global user.email "code@posthog.com" && \
9191
# Block `git commit`/`git push` so unsigned commits cannot leave the sandbox.
9292
COPY products/tasks/backend/sandbox/images/git-guard.sh /opt/posthog/bin/git
9393
RUN chmod +x /opt/posthog/bin/git
94+
# Inject the per-actor GitHub token into every `gh` call (BASH_ENV only reaches
95+
# non-interactive `bash -c`; the agent's interactive shell needs this shim).
96+
COPY products/tasks/backend/sandbox/images/gh-guard.sh /opt/posthog/bin/gh
97+
RUN chmod +x /opt/posthog/bin/gh
9498
ENV PATH="/opt/posthog/bin:${PATH}"
9599

96100
# This is required for the Claude Code SDK to allow --dangerously-skip-permissions as the root user
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash
2+
# gh guard.
3+
#
4+
# Installed first on PATH inside the cloud sandbox image as /opt/posthog/bin/gh.
5+
# The backend delivers the per-actor GitHub token via BASH_ENV, which only
6+
# non-interactive `bash -c` honors — the agent runs its tool commands in an
7+
# interactive shell, so `gh` there would otherwise have no token. This shim
8+
# sources the same credential script the shells do, so `gh` authenticates as the
9+
# current actor regardless of shell mode, and honors logout (an emptied file
10+
# exports nothing, leaving gh unauthenticated rather than falling back to a stale
11+
# token). All arguments pass straight through to the real gh.
12+
13+
native_gh=""
14+
for candidate in /usr/bin/gh /usr/local/bin/gh /bin/gh; do
15+
if [ -x "$candidate" ] && [ "$candidate" != "/opt/posthog/bin/gh" ]; then
16+
native_gh="$candidate"
17+
break
18+
fi
19+
done
20+
if [ -z "$native_gh" ]; then
21+
echo "gh-guard: could not locate the real gh binary" >&2
22+
exit 127
23+
fi
24+
25+
# Re-source the backend-managed credentials fresh on every call (the file is
26+
# rewritten on each refresh / actor transition). The script's sourced branch
27+
# unsets then re-exports GH_TOKEN/GITHUB_TOKEN from the env file.
28+
if [ -f /tmp/agentsh-bash-env.sh ]; then
29+
# shellcheck source=/dev/null
30+
. /tmp/agentsh-bash-env.sh
31+
fi
32+
33+
exec "$native_gh" "$@"

0 commit comments

Comments
 (0)