Skip to content

Commit d0062cd

Browse files
authored
JGC-596 - Hide survey link when invoked by an AI agent (#3588)
Extend ShouldHideSurveyLink to also check ExecutionContext.IsAgent from jfrog-cli-core, so the post-help survey prompt isn't shown when jfrog-cli is invoked by an AI coding agent (Claude Code, Cursor, Gemini CLI, etc.). Also clear agent-detector env vars in existing survey tests so they stay deterministic when run from within an agent shell.
1 parent a0c55a7 commit d0062cd

4 files changed

Lines changed: 80 additions & 3 deletions

File tree

docs/common/env.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ const (
110110

111111
JfrogCliHideSurvey = ` JFROG_CLI_HIDE_SURVEY
112112
[Default: false]
113-
Set to true to hide the survey link that appears after successful command execution.`
113+
Set to true to hide the survey link that appears after successful command execution.
114+
The survey is also automatically hidden when JFrog CLI is invoked by a detected AI agent.`
114115
)
115116

116117
var (

main_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,34 @@ func TestDockerScanHelp(t *testing.T) {
451451
assert.Contains(t, string(content), "jfrog docker scan - Scan local docker image using the docker client and Xray.")
452452
}
453453

454+
// agentDetectorEnvVars lists every env var jfrog-cli-core's agent detector consults
455+
// (see ExecutionContext in jfrog-cli-core/common/commands). Tests clear these so
456+
// survey-visibility assertions are deterministic regardless of the shell running
457+
// `go test` (e.g. running inside Claude Code, Cursor, etc.).
458+
var agentDetectorEnvVars = []string{
459+
"CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT",
460+
"GEMINI_CLI",
461+
"GOOSE_TERMINAL",
462+
"CURSOR_AGENT", "CURSOR_CLI", "CURSOR_TRACE_ID",
463+
"COPILOT_CLI",
464+
"KILO_IPC_SOCKET_PATH", "KILO_SERVER_PASSWORD",
465+
"ROO_CODE_IPC_SOCKET_PATH",
466+
"CODEX_CI",
467+
"AGENT",
468+
}
469+
470+
func clearAgentEnvVarsForTest(t *testing.T) {
471+
t.Helper()
472+
for _, e := range agentDetectorEnvVars {
473+
t.Setenv(e, "")
474+
}
475+
commands.ResetExecutionContextForTest()
476+
t.Cleanup(commands.ResetExecutionContextForTest)
477+
}
478+
454479
func TestSurvey_DisplayedOnHelp(t *testing.T) {
455480
t.Setenv("CI", "false")
481+
clearAgentEnvVarsForTest(t)
456482
jfrogCli := coreTests.NewJfrogCli(execMain, "jfrog", "")
457483
_, contentErr, err := tests.GetCmdOutput(t, jfrogCli, "help")
458484
require.NoError(t, err)
@@ -467,6 +493,18 @@ func TestSurvey_NotDisplayedOnHelpCI(t *testing.T) {
467493
assert.NotContains(t, string(contentErr), "https://") // not doing more check as url can change
468494
}
469495

496+
func TestSurvey_NotDisplayedOnHelpAgent(t *testing.T) {
497+
t.Setenv("CI", "false")
498+
clearAgentEnvVarsForTest(t)
499+
t.Setenv("CLAUDECODE", "true")
500+
commands.ResetExecutionContextForTest()
501+
502+
jfrogCli := coreTests.NewJfrogCli(execMain, "jfrog", "")
503+
_, contentErr, err := tests.GetCmdOutput(t, jfrogCli, "help")
504+
require.NoError(t, err)
505+
assert.NotContains(t, string(contentErr), "https://") // not doing more check as url can change
506+
}
507+
470508
func TestGenerateAndLogTraceIdToken(t *testing.T) {
471509
traceIdToken, err := generateTraceIdToken()
472510
assert.NoError(t, err)

utils/cliutils/utils.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,9 @@ func GetJFrogApplicationKey(c *cli.Context) string {
777777
return applicationKey
778778
}
779779

780-
// ShouldHideSurveyLink checks if the survey should be hidden based on the JFROG_CLI_HIDE_SURVEY and CI environment variables
780+
// ShouldHideSurveyLink checks if the survey should be hidden based on the JFROG_CLI_HIDE_SURVEY
781+
// and CI environment variables, or when the CLI is invoked by an AI agent.
781782
// Returns true if the survey should be hidden, false otherwise
782783
func ShouldHideSurveyLink() bool {
783-
return getCiValue() || os.Getenv(JfrogCliHideSurvey) == "true"
784+
return getCiValue() || os.Getenv(JfrogCliHideSurvey) == "true" || commonCommands.DetectExecutionContext().IsAgent
784785
}

utils/cliutils/utils_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
biutils "github.com/jfrog/build-info-go/utils"
16+
corecommands "github.com/jfrog/jfrog-cli-core/v2/common/commands"
1617
configtests "github.com/jfrog/jfrog-cli-core/v2/utils/config/tests"
1718
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
1819
clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests"
@@ -385,6 +386,31 @@ func (t *redirectingTransport) RoundTrip(req *http.Request) (*http.Response, err
385386
return t.baseTransport.RoundTrip(req)
386387
}
387388

389+
// agentDetectorEnvVars lists every env var jfrog-cli-core's agent detector consults
390+
// (see ExecutionContext in jfrog-cli-core/common/commands). Tests clear these so
391+
// ShouldHideSurveyLink's agent check is deterministic regardless of the shell
392+
// running `go test` (e.g. running inside Claude Code, Cursor, etc.).
393+
var agentDetectorEnvVars = []string{
394+
"CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT",
395+
"GEMINI_CLI",
396+
"GOOSE_TERMINAL",
397+
"CURSOR_AGENT", "CURSOR_CLI", "CURSOR_TRACE_ID",
398+
"COPILOT_CLI",
399+
"KILO_IPC_SOCKET_PATH", "KILO_SERVER_PASSWORD",
400+
"ROO_CODE_IPC_SOCKET_PATH",
401+
"CODEX_CI",
402+
"AGENT",
403+
}
404+
405+
func clearAgentEnvVarsForTest(t *testing.T) {
406+
t.Helper()
407+
for _, e := range agentDetectorEnvVars {
408+
t.Setenv(e, "")
409+
}
410+
corecommands.ResetExecutionContextForTest()
411+
t.Cleanup(corecommands.ResetExecutionContextForTest)
412+
}
413+
388414
// TestGetHasDisplayedSurveyLink tests the survey link environment variable check with parametrized test cases
389415
func TestGetHasDisplayedSurveyLink(t *testing.T) {
390416
testCases := []struct {
@@ -411,6 +437,7 @@ func TestGetHasDisplayedSurveyLink(t *testing.T) {
411437
t.Setenv(coreutils.CI, "")
412438
for _, tc := range testCases {
413439
t.Run(tc.name, func(t *testing.T) {
440+
clearAgentEnvVarsForTest(t)
414441
t.Setenv(JfrogCliHideSurvey, tc.envValue)
415442

416443
shouldHide := ShouldHideSurveyLink()
@@ -430,6 +457,16 @@ func TestSettingCIFlagRemovesSurvey(t *testing.T) {
430457
assert.True(t, shouldHide, "Expected survey to be hidden when CI flag is set")
431458
}
432459

460+
func TestSurveyHiddenForAgent(t *testing.T) {
461+
t.Setenv(coreutils.CI, "")
462+
t.Setenv(JfrogCliHideSurvey, "")
463+
clearAgentEnvVarsForTest(t)
464+
t.Setenv("CLAUDECODE", "true")
465+
corecommands.ResetExecutionContextForTest()
466+
467+
assert.True(t, ShouldHideSurveyLink(), "Expected survey to be hidden when invoked by an agent")
468+
}
469+
433470
func TestLoginCommandFlagsIncludeServerId(t *testing.T) {
434471
flags := GetCommandFlags(Login)
435472
assert.NotEmpty(t, flags, "Expected login command to have flags")

0 commit comments

Comments
 (0)