Skip to content

Commit 7ed2ee6

Browse files
GuyMosesclaude
andcommitted
test(codex): drive the e2e canary through install-codex.sh end to end
Instead of calling emit-codex-hooks directly, TestE2EFullFlowWithCodex now runs the real install-codex.sh against a hermetic HOME + XDG_STATE_HOME (binary + bootstrap pre-staged so the not-yet-published release download is skipped), then drives live Codex with no bypass against what the installer produced. One test now covers the whole path — installer → config.toml merge + pre-trust → live Codex → OTLP — so a break anywhere surfaces here. Removes the writeCodexHooksTrusted shim in favor of installCodex + codexPluginVersion/unameOSArch helpers. SIG-171 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c75266a commit 7ed2ee6

1 file changed

Lines changed: 82 additions & 39 deletions

File tree

test/e2e/codex_e2e_test.go

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,16 @@ func TestE2EFullFlowWithCodex(t *testing.T) {
5656

5757
pluginDir := findPluginDir(t)
5858

59-
// Hermetic Codex home so we never touch the developer's real ~/.codex.
60-
codexHome := t.TempDir()
59+
// Hermetic HOME + state so install-codex.sh writes to a throwaway ~/.codex and
60+
// never touches the developer's real config.
61+
home := t.TempDir()
62+
state := t.TempDir()
63+
codexHome := filepath.Join(home, ".codex")
64+
require.NoError(t, os.MkdirAll(codexHome, 0o755))
6165
if !authenticateCodex(t, codexBin, codexHome) {
6266
t.Fatal("no Codex auth available — set OPENAI_API_KEY (CI: a service-account key) or run `codex login` (local)")
6367
}
6468

65-
// Build the Codex entrypoint binary.
66-
binary := filepath.Join(t.TempDir(), "codex-on-event")
67-
build := exec.Command("go", "build", "-o", binary, "./cmd/codex-on-event")
68-
build.Dir = pluginDir
69-
out, err := build.CombinedOutput()
70-
require.NoError(t, err, "build failed: %s", string(out))
71-
7269
// Mock OTLP server records every request body.
7370
var (
7471
mu sync.Mutex
@@ -83,24 +80,11 @@ func TestE2EFullFlowWithCodex(t *testing.T) {
8380
}))
8481
defer srv.Close()
8582

86-
pluginData := t.TempDir()
87-
88-
// Bootstrap wrapper: injects OTLP config into the environment and execs the
89-
// binary. Codex runs this as the hook command and pipes the event on stdin.
90-
wrapper := filepath.Join(t.TempDir(), "codex-on-event-wrapper.sh")
91-
wrapperScript := fmt.Sprintf(`#!/usr/bin/env bash
92-
export DASH0_OTLP_URL=%q
93-
export CODEX_PLUGIN_OPTION_AUTH_TOKEN="e2e-codex-token"
94-
export DASH0_PLUGIN_DATA=%q
95-
export DASH0_OMIT_IO="false"
96-
exec %q
97-
`, srv.URL, pluginData, binary)
98-
require.NoError(t, os.WriteFile(wrapper, []byte(wrapperScript), 0o755))
99-
100-
// Install hooks the customer way: register in config.toml AND pre-trust them
101-
// via the installer's own emit path. The command written must match what we
102-
// hash, so emit owns both. No bypass flag below — this is the real path.
103-
writeCodexHooksTrusted(t, codexHome, binary, fmt.Sprintf("bash %q", wrapper))
83+
// Install exactly as a customer would: run install-codex.sh, which appends the
84+
// hooks + reproduced trust to ~/.codex/config.toml and writes the creds config.
85+
// This drives the whole path in one test — installer → config.toml → live Codex
86+
// → OTLP — so a break anywhere (config merge, trust hash, hook contract) fails here.
87+
installCodex(t, pluginDir, home, state, srv.URL, "e2e-codex-token")
10488

10589
// Work in a throwaway git repo so the agent has somewhere to write.
10690
workDir := t.TempDir()
@@ -109,16 +93,18 @@ exec %q
10993
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
11094
defer cancel()
11195

112-
// NOTE: deliberately NO --dangerously-bypass-hook-trust — the hooks above are
113-
// pre-trusted, exactly as a customer install leaves them.
96+
// NOTE: deliberately NO --dangerously-bypass-hook-trust — install-codex.sh
97+
// pre-trusted the hooks, exactly as a real install leaves them.
11498
cmd := exec.CommandContext(ctx, codexBin, "exec",
11599
"-s", "workspace-write",
116100
"-c", "approval_policy=\"never\"",
117101
"-C", workDir,
118102
"Create a file hello.txt containing exactly the text 'hi from codex', then run the shell command 'cat hello.txt'. Keep it brief.",
119103
)
120-
cmd.Env = append(os.Environ(), "CODEX_HOME="+codexHome)
121-
out, err = cmd.CombinedOutput()
104+
// The hook (bootstrap → binary) inherits this env: HOME locates the creds
105+
// config, XDG_STATE_HOME the installed binary, CODEX_HOME the config.toml.
106+
cmd.Env = append(os.Environ(), "HOME="+home, "XDG_STATE_HOME="+state, "CODEX_HOME="+codexHome)
107+
out, err := cmd.CombinedOutput()
122108
t.Logf("codex exec output (err=%v):\n%s", err, string(out))
123109
require.NoError(t, err, "codex exec failed")
124110

@@ -189,15 +175,72 @@ func spanHasPositiveTokenUsage(s otlp.Span) bool {
189175
return false
190176
}
191177

192-
// writeCodexHooksTrusted writes CODEX_HOME/config.toml using the installer's own
193-
// emit path: hooks + reproduced [hooks.state] trusted_hash for the given command.
194-
func writeCodexHooksTrusted(t *testing.T, codexHome, binary, command string) {
178+
// installCodex runs install-codex.sh the customer way against a hermetic HOME +
179+
// XDG_STATE_HOME, so it appends hooks + reproduced trust to $HOME/.codex/config.toml
180+
// and writes the creds config. The version-pinned binary and the bootstrap are
181+
// pre-staged so the script skips the release download (none exists until M4);
182+
// everything else — the config.toml merge, trust emission, and creds file — is
183+
// the real installer exercised end to end.
184+
func installCodex(t *testing.T, pluginDir, home, state, otlpURL, token string) {
185+
t.Helper()
186+
ver := codexPluginVersion(t, pluginDir)
187+
goos, arch := unameOSArch(t)
188+
189+
codexState := filepath.Join(state, "dash0-agent-plugin", "codex")
190+
require.NoError(t, os.MkdirAll(filepath.Join(codexState, "bin"), 0o755))
191+
binPath := filepath.Join(codexState, "bin", fmt.Sprintf("codex-on-event-%s-%s-%s", ver, goos, arch))
192+
build := exec.Command("go", "build", "-o", binPath, "./cmd/codex-on-event")
193+
build.Dir = pluginDir
194+
if out, err := build.CombinedOutput(); err != nil {
195+
t.Fatalf("build failed: %s", string(out))
196+
}
197+
bootstrap, err := os.ReadFile(filepath.Join(pluginDir, "scripts", "codex-on-event.sh"))
198+
require.NoError(t, err)
199+
require.NoError(t, os.WriteFile(filepath.Join(codexState, "codex-on-event.sh"), bootstrap, 0o755))
200+
201+
cmd := exec.Command("bash", filepath.Join(pluginDir, "install-codex.sh"))
202+
cmd.Env = append(os.Environ(),
203+
"HOME="+home, "XDG_STATE_HOME="+state,
204+
"DASH0_VERSION="+ver, "DASH0_OTLP_URL="+otlpURL,
205+
"DASH0_AUTH_TOKEN="+token, "DASH0_DATASET=default",
206+
)
207+
out, err := cmd.CombinedOutput()
208+
t.Logf("install-codex.sh output:\n%s", string(out))
209+
require.NoError(t, err, "install-codex.sh failed")
210+
}
211+
212+
// codexPluginVersion reads the pinned VERSION from scripts/codex-on-event.sh so
213+
// the pre-staged binary path matches what install-codex.sh derives.
214+
func codexPluginVersion(t *testing.T, pluginDir string) string {
195215
t.Helper()
196-
configPath := filepath.Join(codexHome, "config.toml")
197-
cmd := exec.Command(binary, "emit-codex-hooks", "--config", configPath, "--command", command)
198-
block, err := cmd.CombinedOutput()
199-
require.NoError(t, err, "emit-codex-hooks failed: %s", string(block))
200-
require.NoError(t, os.WriteFile(configPath, block, 0o644))
216+
data, err := os.ReadFile(filepath.Join(pluginDir, "scripts", "codex-on-event.sh"))
217+
require.NoError(t, err)
218+
for _, line := range strings.Split(string(data), "\n") {
219+
if strings.HasPrefix(line, "VERSION=") {
220+
return strings.Trim(strings.TrimPrefix(line, "VERSION="), `"`)
221+
}
222+
}
223+
t.Fatal("VERSION= not found in scripts/codex-on-event.sh")
224+
return ""
225+
}
226+
227+
// unameOSArch mirrors install-codex.sh's platform detection so the pre-staged
228+
// binary name matches exactly.
229+
func unameOSArch(t *testing.T) (string, string) {
230+
t.Helper()
231+
osOut, err := exec.Command("uname", "-s").Output()
232+
require.NoError(t, err)
233+
archOut, err := exec.Command("uname", "-m").Output()
234+
require.NoError(t, err)
235+
goos := strings.ToLower(strings.TrimSpace(string(osOut)))
236+
arch := strings.TrimSpace(string(archOut))
237+
switch arch {
238+
case "x86_64":
239+
arch = "amd64"
240+
case "aarch64", "arm64":
241+
arch = "arm64"
242+
}
243+
return goos, arch
201244
}
202245

203246
// authenticateCodex sets up auth inside a hermetic CODEX_HOME. Returns false

0 commit comments

Comments
 (0)