Skip to content

Commit 73b4353

Browse files
committed
fix(tasks): install the gh shim at runtime for snapshot resumes
The gh PATH shim is baked into new base images, but a resume from a pre-shim filesystem snapshot — or any window where the base image lags this backend — would lack it, leaving gh with no token once the frozen launch-env token is unset. Write and chmod the shim from start_agent_server (reading its single source of truth) so it's present on every launch regardless of image age.
1 parent f0a36b5 commit 73b4353

5 files changed

Lines changed: 39 additions & 0 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import shlex
2+
from pathlib import Path
23
from urllib.parse import urlparse
34

45
from django.conf import settings
@@ -16,6 +17,19 @@
1617
# Sourced via BASH_ENV on every `bash -c` the agent runs, so git/gh pick up a
1718
# mid-session GitHub credential refresh from its dedicated credential file.
1819
BASH_ENV_SCRIPT = "/tmp/agentsh-bash-env.sh"
20+
21+
# The gh PATH shim (first on PATH; sources the credential script so gh authenticates as the current
22+
# actor in any shell mode). Baked into new base images, but also installed at runtime so resumes
23+
# from pre-shim filesystem snapshots — and any window where the image lags this backend — still
24+
# deliver the token to gh. Read from its single source of truth (the file the Dockerfiles COPY).
25+
GH_GUARD_INSTALL_PATH = "/opt/posthog/bin/gh"
26+
_GH_GUARD_SOURCE_PATH = Path(__file__).resolve().parents[2] / "sandbox" / "images" / "gh-guard.sh"
27+
28+
29+
def read_gh_guard_script() -> bytes:
30+
return _GH_GUARD_SOURCE_PATH.read_bytes()
31+
32+
1933
AGENTSH_AUDIT_DB = "/var/lib/agentsh/events.db"
2034
INFRASTRUCTURE_DOMAINS = [
2135
"*.posthog.com",

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@
3535
from .agentsh import (
3636
BASH_ENV_SCRIPT,
3737
ENV_WRAPPER_SCRIPT,
38+
GH_GUARD_INSTALL_PATH,
3839
SESSION_ID_FILE,
3940
build_exec_prefix,
4041
build_setup_script,
4142
generate_bash_env_script,
4243
generate_config_yaml,
4344
generate_env_wrapper,
4445
generate_policy_yaml,
46+
read_gh_guard_script,
4547
)
4648
from .local_skills import ENV_LOCAL_SKILLS_HOST_PATH, LocalSkillsCache, bundled_skills_disabled
4749
from .sandbox import (
@@ -878,6 +880,15 @@ def _launch_and_check(self, command: str) -> bool:
878880
return False
879881
return self._wait_for_health_check(max_attempts=20)
880882

883+
def _install_gh_guard(self) -> None:
884+
"""Install the gh PATH shim at runtime so it's present regardless of image age.
885+
886+
New base images bake it in, but a resume from a pre-shim filesystem snapshot (or any window
887+
where the image lags this backend) would otherwise lack it, leaving gh with no token once the
888+
frozen launch-env token is unset."""
889+
self.write_file(GH_GUARD_INSTALL_PATH, read_gh_guard_script())
890+
self.execute(f"chmod +x {shlex.quote(GH_GUARD_INSTALL_PATH)}", timeout_seconds=30)
891+
881892
def start_agent_server(
882893
self,
883894
repository: str | None,
@@ -926,6 +937,7 @@ def start_agent_server(
926937
# mid-session credential refreshes reach git/gh. Needed for both agentsh
927938
# and non-agentsh runs.
928939
self.write_file(BASH_ENV_SCRIPT, generate_bash_env_script().encode())
940+
self._install_gh_guard()
929941

930942
if allowed_domains is not None:
931943
self._setup_agentsh(WORKING_DIR, allowed_domains)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
AGENTSH_DAEMON_PORT,
5757
BASH_ENV_SCRIPT,
5858
ENV_WRAPPER_SCRIPT,
59+
GH_GUARD_INSTALL_PATH,
5960
SESSION_ID_FILE,
6061
_hostname_from_url,
6162
build_exec_prefix,
@@ -64,6 +65,7 @@
6465
generate_config_yaml,
6566
generate_env_wrapper,
6667
generate_policy_yaml,
68+
read_gh_guard_script,
6769
)
6870
from products.tasks.backend.logic.services.local_packages import (
6971
get_local_package_runtime_dependencies,
@@ -1106,6 +1108,11 @@ def start_agent_server(
11061108
repo_path = f"/tmp/workspace/repos/{org}/{repo}"
11071109

11081110
self.write_file(BASH_ENV_SCRIPT, generate_bash_env_script().encode())
1111+
# Install the gh shim at runtime too (see agentsh.GH_GUARD_INSTALL_PATH): a resume from a
1112+
# pre-shim filesystem snapshot — or any window where the base image lags this backend —
1113+
# would otherwise leave gh with no token once the frozen launch-env token is unset.
1114+
self.write_file(GH_GUARD_INSTALL_PATH, read_gh_guard_script())
1115+
self.execute(f"chmod +x {shlex.quote(GH_GUARD_INSTALL_PATH)}", timeout_seconds=30)
11091116

11101117
if allowed_domains is not None:
11111118
self._setup_agentsh(WORKING_DIR, allowed_domains)

products/tasks/backend/logic/services/tests/test_modal_sandbox.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,8 @@ def test_start_agent_server_raises_on_health_check_failure(self, mock_sandbox: A
665665
mock_sandbox.execute = MagicMock(
666666
side_effect=[
667667
ExecutionResult(stdout="", stderr="", exit_code=0, error=None),
668+
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # gh shim write (mv)
669+
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # gh shim chmod
668670
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # --posthogExecPermissionRegex probe
669671
ExecutionResult(stdout="", stderr="", exit_code=1, error=None),
670672
ExecutionResult(stdout="some log output", stderr="", exit_code=0, error=None),
@@ -720,6 +722,8 @@ def test_start_agent_server_frees_port_before_relaunch(self, mock_sandbox: Any):
720722
mock_sandbox.execute = MagicMock(
721723
side_effect=[
722724
ExecutionResult(stdout="", stderr="", exit_code=0, error=None),
725+
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # gh shim write (mv)
726+
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # gh shim chmod
723727
ExecutionResult(stdout="", stderr="", exit_code=0, error=None), # --posthogExecPermissionRegex probe
724728
ExecutionResult(stdout="", stderr="", exit_code=0, error=None),
725729
ExecutionResult(stdout="ok:1", stderr="", exit_code=0, error=None),

products/tasks/backend/tests/test_agentsh.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ def execute(command: str, timeout_seconds: int | None = None) -> ExecutionResult
463463
if "--taskId" in command:
464464
launched.append(command)
465465
return ExecutionResult(stdout="", stderr="", exit_code=0)
466+
if "chmod" in command: # gh shim install
467+
return ExecutionResult(stdout="", stderr="", exit_code=0)
466468
self.assertIn("grep", command)
467469
return ExecutionResult(stdout="", stderr="", exit_code=0 if supported else 1)
468470

0 commit comments

Comments
 (0)