From 5bd98a810b577ddba728e836db97254cff3a504d Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 17 Mar 2026 22:47:10 -0700 Subject: [PATCH 1/3] fix: unquote environment variable passed to hook commands --- internal/hooks/hook_executor_default_test.go | 8 ++++---- internal/hooks/hook_executor_v2_test.go | 12 ++++++------ internal/hooks/hooks.go | 4 +++- internal/pkg/platform/localserver.go | 5 +++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/internal/hooks/hook_executor_default_test.go b/internal/hooks/hook_executor_default_test.go index dba9cf9a..6c343a70 100644 --- a/internal/hooks/hook_executor_default_test.go +++ b/internal/hooks/hook_executor_default_test.go @@ -60,8 +60,8 @@ func Test_Hook_Execute_Default_Protocol(t *testing.T) { opts: HookExecOpts{ Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ - "batman": "robin", - "yin": "yang", + "BATMAN": "robin hood", + "YIN": "yang", }, Exec: &MockExec{ mockCommand: &MockCommand{ @@ -74,8 +74,8 @@ func Test_Hook_Execute_Default_Protocol(t *testing.T) { response, err := executor.Execute(ctx, opts) require.Equal(t, "test output", response) require.Equal(t, nil, err) - require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `batman="robin"`) - require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `yin="yang"`) + require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `BATMAN=robin hood`) + require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `YIN=yang`) }, }, "failed execution": { diff --git a/internal/hooks/hook_executor_v2_test.go b/internal/hooks/hook_executor_v2_test.go index ee44088a..bd1c21a7 100644 --- a/internal/hooks/hook_executor_v2_test.go +++ b/internal/hooks/hook_executor_v2_test.go @@ -55,8 +55,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { opts: HookExecOpts{ Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ - "batman": "robin", - "yin": "yang", + "BATMAN": "robin hood", + "YIN": "yang", }, Exec: &MockExec{ mockCommand: &MockCommand{ @@ -68,8 +68,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { check: func(t *testing.T, response string, err error, mockExec ExecInterface) { require.Equal(t, `{"message": "hello world"}`, response) require.Equal(t, nil, err) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `batman="robin"`) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `yin="yang"`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `BATMAN=robin hood`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `YIN=yang`) }, }, "successful execution with payload > 64kb": { @@ -89,8 +89,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { check: func(t *testing.T, response string, err error, mockExec ExecInterface) { require.Equal(t, sixtyFourKBString, response) require.Equal(t, nil, err) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `batman="robin"`) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `yin="yang"`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `batman=robin`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `yin=yang`) }, }, "successful execution with payload > 512kb": { diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go index dcf3a33a..49ebf8a6 100644 --- a/internal/hooks/hooks.go +++ b/internal/hooks/hooks.go @@ -57,7 +57,9 @@ func processExecOpts(opts HookExecOpts) ([]string, []string, []string, error) { // To avoid removing any environment variables that are set in the current environment, we first set the cmd.Env to the current environment. // before adding any new environment variables. var cmdEnvVars = os.Environ() - cmdEnvVars = append(cmdEnvVars, goutils.MapToStringSlice(opts.Env, "")...) + for name, value := range opts.Env { + cmdEnvVars = append(cmdEnvVars, name+"="+value) + } return cmdArgs, cmdArgVars, cmdEnvVars, nil } diff --git a/internal/pkg/platform/localserver.go b/internal/pkg/platform/localserver.go index ed1735bc..28b87041 100644 --- a/internal/pkg/platform/localserver.go +++ b/internal/pkg/platform/localserver.go @@ -30,7 +30,6 @@ import ( "github.com/gorilla/websocket" "github.com/radovskyb/watcher" "github.com/slackapi/slack-cli/internal/config" - "github.com/slackapi/slack-cli/internal/goutils" "github.com/slackapi/slack-cli/internal/hooks" "github.com/slackapi/slack-cli/internal/iostreams" "github.com/slackapi/slack-cli/internal/pkg/apps" @@ -307,7 +306,9 @@ func (r *LocalServer) StartDelegate(ctx context.Context) error { // To avoid removing any environment variables that are set in the current environment, we first set the cmd.Env to the current environment. // before adding any new environment variables. var cmdEnvVars = os.Environ() - cmdEnvVars = append(cmdEnvVars, goutils.MapToStringSlice(sdkManagedConnectionStartHookOpts.Env, "")...) + for name, value := range sdkManagedConnectionStartHookOpts.Env { + cmdEnvVars = append(cmdEnvVars, name+"="+value) + } cmd := sdkManagedConnectionStartHookOpts.Exec.Command(cmdEnvVars, os.Stdout, os.Stderr, nil, cmdArgs[0], cmdArgVars...) // Store command reference for lifecycle management From d2f878be60be0df1ae698d61e6f3b67ddd30ac80 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 23 Mar 2026 11:51:11 -0700 Subject: [PATCH 2/3] test: continue preference to uppercase keys for environment Co-authored-by: Michael Brooks --- internal/hooks/hook_executor_v2_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/hooks/hook_executor_v2_test.go b/internal/hooks/hook_executor_v2_test.go index bd1c21a7..4e91e80b 100644 --- a/internal/hooks/hook_executor_v2_test.go +++ b/internal/hooks/hook_executor_v2_test.go @@ -77,7 +77,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ "batman": "robin", - "yin": "yang", +"BATMAN": "robin", +"YIN": "yang", }, Exec: &MockExec{ mockCommand: &MockCommand{ @@ -89,8 +90,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { check: func(t *testing.T, response string, err error, mockExec ExecInterface) { require.Equal(t, sixtyFourKBString, response) require.Equal(t, nil, err) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `batman=robin`) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `yin=yang`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `BATMAN=robin`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `YIN=yang`) }, }, "successful execution with payload > 512kb": { From 28ff7f1b11ab9d047d617b900345c4aac1a833e0 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 23 Mar 2026 12:18:08 -0700 Subject: [PATCH 3/3] test: confirm variables with space still arrives --- internal/hooks/hook_executor_default_test.go | 6 ++++-- internal/hooks/hook_executor_v2_test.go | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/internal/hooks/hook_executor_default_test.go b/internal/hooks/hook_executor_default_test.go index 6c343a70..9556287d 100644 --- a/internal/hooks/hook_executor_default_test.go +++ b/internal/hooks/hook_executor_default_test.go @@ -60,7 +60,8 @@ func Test_Hook_Execute_Default_Protocol(t *testing.T) { opts: HookExecOpts{ Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ - "BATMAN": "robin hood", + "BATMAN": "robin", + "WHOAMI": "lumpy space princess", "YIN": "yang", }, Exec: &MockExec{ @@ -74,7 +75,8 @@ func Test_Hook_Execute_Default_Protocol(t *testing.T) { response, err := executor.Execute(ctx, opts) require.Equal(t, "test output", response) require.Equal(t, nil, err) - require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `BATMAN=robin hood`) + require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `BATMAN=robin`) + require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `WHOAMI=lumpy space princess`) require.Contains(t, opts.Exec.(*MockExec).mockCommand.Env, `YIN=yang`) }, }, diff --git a/internal/hooks/hook_executor_v2_test.go b/internal/hooks/hook_executor_v2_test.go index 4e91e80b..8b83db94 100644 --- a/internal/hooks/hook_executor_v2_test.go +++ b/internal/hooks/hook_executor_v2_test.go @@ -55,7 +55,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { opts: HookExecOpts{ Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ - "BATMAN": "robin hood", + "BATMAN": "robin", + "WHOAMI": "lumpy space princess", "YIN": "yang", }, Exec: &MockExec{ @@ -68,7 +69,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { check: func(t *testing.T, response string, err error, mockExec ExecInterface) { require.Equal(t, `{"message": "hello world"}`, response) require.Equal(t, nil, err) - require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `BATMAN=robin hood`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `BATMAN=robin`) + require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `WHOAMI=lumpy space princess`) require.Contains(t, mockExec.(*MockExec).mockCommand.Env, `YIN=yang`) }, }, @@ -76,9 +78,8 @@ func Test_Hook_Execute_V2_Protocol(t *testing.T) { opts: HookExecOpts{ Hook: HookScript{Name: "happypath", Command: "echo {}"}, Env: map[string]string{ - "batman": "robin", -"BATMAN": "robin", -"YIN": "yang", + "BATMAN": "robin", + "YIN": "yang", }, Exec: &MockExec{ mockCommand: &MockCommand{