Skip to content

Commit 207d3c5

Browse files
authored
Merge pull request #147 from AsakiEmura/fix/unset-claudecode-env
fix(executor): unset CLAUDECODE env to prevent nested session rejection
2 parents 1dd7b23 + 5fe8c24 commit 207d3c5

4 files changed

Lines changed: 58 additions & 0 deletions

File tree

codeagent-wrapper/internal/app/executor_concurrent_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ func (f *execFakeRunner) Process() executor.ProcessHandle {
169169
return &execFakeProcess{pid: 1}
170170
}
171171

172+
func (f *execFakeRunner) UnsetEnv(keys ...string) {
173+
for _, k := range keys {
174+
delete(f.env, k)
175+
}
176+
}
177+
172178
func TestExecutorRunCodexTaskWithContext(t *testing.T) {
173179
defer resetTestHooks()
174180

codeagent-wrapper/internal/app/main_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ func (d *drainBlockingCmd) Process() executor.ProcessHandle {
274274
return d.inner.Process()
275275
}
276276

277+
func (d *drainBlockingCmd) UnsetEnv(keys ...string) {
278+
d.inner.UnsetEnv(keys...)
279+
}
280+
277281
type bufferWriteCloser struct {
278282
buf bytes.Buffer
279283
mu sync.Mutex
@@ -568,6 +572,14 @@ func (f *fakeCmd) Process() executor.ProcessHandle {
568572
return f.process
569573
}
570574

575+
func (f *fakeCmd) UnsetEnv(keys ...string) {
576+
f.mu.Lock()
577+
defer f.mu.Unlock()
578+
for _, k := range keys {
579+
delete(f.env, k)
580+
}
581+
}
582+
571583
func (f *fakeCmd) runStdoutScript() {
572584
if len(f.stdoutPlan) == 0 {
573585
if !f.keepStdoutOpen {

codeagent-wrapper/internal/executor/env_stderr_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ func (f *fakeCmd) SetEnv(env map[string]string) {
4141
}
4242
}
4343
func (f *fakeCmd) Process() processHandle { return nil }
44+
func (f *fakeCmd) UnsetEnv(keys ...string) {
45+
for _, k := range keys {
46+
delete(f.env, k)
47+
}
48+
}
4449

4550
func TestEnvInjection_LogsToStderrAndMasksKey(t *testing.T) {
4651
// Arrange ~/.codeagent/models.json via HOME override.

codeagent-wrapper/internal/executor/executor.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type commandRunner interface {
113113
SetStderr(io.Writer)
114114
SetDir(string)
115115
SetEnv(env map[string]string)
116+
UnsetEnv(keys ...string)
116117
Process() processHandle
117118
}
118119

@@ -221,6 +222,33 @@ func (r *realCmd) SetEnv(env map[string]string) {
221222
r.cmd.Env = out
222223
}
223224

225+
func (r *realCmd) UnsetEnv(keys ...string) {
226+
if r == nil || r.cmd == nil || len(keys) == 0 {
227+
return
228+
}
229+
// If cmd.Env is nil, Go inherits all parent env vars.
230+
// Populate explicitly so we can selectively remove keys.
231+
if r.cmd.Env == nil {
232+
r.cmd.Env = os.Environ()
233+
}
234+
drop := make(map[string]struct{}, len(keys))
235+
for _, k := range keys {
236+
drop[k] = struct{}{}
237+
}
238+
filtered := make([]string, 0, len(r.cmd.Env))
239+
for _, kv := range r.cmd.Env {
240+
idx := strings.IndexByte(kv, '=')
241+
name := kv
242+
if idx >= 0 {
243+
name = kv[:idx]
244+
}
245+
if _, ok := drop[name]; !ok {
246+
filtered = append(filtered, kv)
247+
}
248+
}
249+
r.cmd.Env = filtered
250+
}
251+
224252
func (r *realCmd) Process() processHandle {
225253
if r == nil || r.cmd == nil || r.cmd.Process == nil {
226254
return nil
@@ -1126,6 +1154,13 @@ func RunCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
11261154

11271155
injectTempEnv(cmd)
11281156

1157+
// Claude Code sets CLAUDECODE=1 in its child processes. If we don't
1158+
// remove it, the spawned `claude -p` detects the variable and refuses
1159+
// to start ("cannot be launched inside another Claude Code session").
1160+
if commandName == "claude" {
1161+
cmd.UnsetEnv("CLAUDECODE")
1162+
}
1163+
11291164
// For backends that don't support -C flag (claude, gemini), set working directory via cmd.Dir
11301165
// Codex passes workdir via -C flag, so we skip setting Dir for it to avoid conflicts
11311166
if cfg.Mode != "resume" && commandName != "codex" && cfg.WorkDir != "" {

0 commit comments

Comments
 (0)