@@ -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