Detailed changes
@@ -195,6 +195,13 @@ func TestBuildEnv(t *testing.T) {
require.Equal(t, "/project", envMap["CRUSH_PROJECT_DIR"])
require.Equal(t, "ls", envMap["CRUSH_TOOL_INPUT_COMMAND"])
require.Equal(t, "/tmp/f.txt", envMap["CRUSH_TOOL_INPUT_FILE_PATH"])
+
+ // Shared Crush markers must be present so hook-authored scripts can
+ // detect they're running under Crush the same way bash-tool-invoked
+ // scripts can.
+ require.Equal(t, "1", envMap["CRUSH"])
+ require.Equal(t, "crush", envMap["AGENT"])
+ require.Equal(t, "crush", envMap["AI_AGENT"])
}
func splitFirst(s, sep string) []string {
@@ -7,6 +7,7 @@ import (
"os"
"strings"
+ "github.com/charmbracelet/crush/internal/shell"
"github.com/tidwall/gjson"
)
@@ -51,6 +52,7 @@ func BuildPayload(eventName, sessionID, cwd, toolName, toolInputJSON string) []b
// It includes all current process env vars plus hook-specific ones.
func BuildEnv(eventName, toolName, sessionID, cwd, projectDir, toolInputJSON string) []string {
env := os.Environ()
+ env = append(env, shell.CrushEnvMarkers()...)
env = append(env,
fmt.Sprintf("CRUSH_EVENT=%s", eventName),
fmt.Sprintf("CRUSH_TOOL_NAME=%s", toolName),
@@ -34,6 +34,20 @@ const (
ShellTypePowerShell
)
+// CrushEnvMarkers returns a fresh slice of the environment variables that
+// Crush unconditionally sets on every shell it spawns — both the interactive
+// bash tool's [Shell] and the hook runner's [Run] calls. Tools that want to
+// detect "am I being invoked by an AI agent?" can check any of these.
+// Keeping them in one place guarantees the two shell surfaces cannot drift.
+// A fresh slice is returned on every call so callers may append freely.
+func CrushEnvMarkers() []string {
+ return []string{
+ "CRUSH=1",
+ "AGENT=crush",
+ "AI_AGENT=crush",
+ }
+}
+
// Logger interface for optional logging
type Logger interface {
InfoPersist(msg string, keysAndValues ...any)
@@ -81,12 +95,7 @@ func NewShell(opts *Options) *Shell {
}
// Allow tools to detect execution by Crush.
- env = append(
- env,
- "CRUSH=1",
- "AGENT=crush",
- "AI_AGENT=crush",
- )
+ env = append(env, CrushEnvMarkers()...)
logger := opts.Logger
if logger == nil {