Skip to content

[SSH] Support agents in non-interactive sessions - #7123

Open
rclarey wants to merge 6 commits into
mainfrom
non-interactive-ssh-agent
Open

rclarey wants to merge 6 commits into
mainfrom
non-interactive-ssh-agent

Conversation

@rclarey

@rclarey rclarey commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Changes

Remove the interactive-only restriction for agent shims by putting the agent shims on PATH via SetEnv on the server-side sshd (rather than adding export PATH="..." to the SSH command sent by the client)

Why

To allow agents to be used in more places

Tests

added unit tests

@rclarey
rclarey requested review from a team as code owners October 20, 2026 11:42
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 3f03a6d

Run: 40359439196

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 281 16 7:25
✅​ aws-windows-latest-4core-16gb 283 14 4:24
✅​ azure linux-2core-8gb 280 16 8:05
✅​ azure-windows-latest-4core-16gb 282 14 5:03
✅​ gcp linux-2core-8gb 281 16 7:42
✅​ gcp-windows-latest-4core-16gb 283 14 5:40
Top 6 slowest tests (at least 2 minutes):
duration env testname
4:57 azure-windows-latest-4core-16gb TestAccept
4:39 gcp-windows-latest-4core-16gb TestAccept
3:50 aws-windows-latest-4core-16gb TestAccept
3:46 gcp linux-2core-8gb TestAccept
3:44 azure linux-2core-8gb TestAccept
3:36 aws linux-2core-8gb TestAccept

@rugpanov rugpanov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nice to have] The Tests section lists only unit tests. These tests do not show that sshd SetEnv puts ~/.agent-shim/bin on PATH on real serverless compute. They also do not show that os.Executable() returns the uploaded binary path there. Add the result of a manual run on serverless with a freshly built binary, for both -- 'claude -p "..."' and a terminal in --ide. As an alternative, add a command -v claude check to the serverless acceptance test that runs with CLOUD_ENV.

// put the Databricks context where the agent reads it, returning any extra argv.
func injectAgentContext(ctx context.Context, home string, agent agentSpec) ([]string, error) {
func injectAgentContext(ctx context.Context, home string, agent agentlauncher.AgentSpec) ([]string, error) {
systemContext := agentSystemContext(env.Get(ctx, workspaceHomeEnv))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] agentSystemContext (line 815) always tells the agent Your working directory is <wsHome>. Only the interactive shell changes into that directory. For databricks ssh connect -- claude -p ..., sshd starts the command in the OS home directory, which does not persist. The same text tells the agent that only /Workspace and /Volumes persist. So the agent can write files with relative paths, think that they persist, and lose them when the compute stops. Use os.Getwd() for the working directory, and name DATABRICKS_WORKSPACE_HOME as the place where files persist.

@@ -0,0 +1 @@
* `databricks ssh connect` on serverless now makes `claude` and `codex` available in IDE sessions (`--ide`) and to non-interactive commands (e.g. `databricks ssh connect --name my-conn -- claude -p "..."`). ([#7123](https://github.com/databricks/cli/pull/7123))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nice to have] ssh joins the remote command arguments with spaces, and the remote shell then splits them again. So -- claude -p "summarize the table" sends three separate arguments to claude, not one prompt. The remote shell also expands characters such as ? or $ in the prompt. Quote the full remote command in the example, for example -- 'claude -p "..."', as acceptance/ssh/connect-serverless-cpu/script does.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Commit: 3f03a6df2 | Workflow run

Regression test report

Selected 1 changed tests (limit=20, 0 not selected)

1 tests: regression=1 unreleased=0 coverage=0 failing=0

test branch main (270b2d0) latest (v1.22.1)
TestAccept/ssh/agent-launchers ✅ ❌ ❌
TestAccept/ssh/agent-launchers: branch ✅ | main (270b2d0) ❌ | latest (v1.22.1) ❌

main (270b2d0):

acceptance_test.go:1311: Diff:
        --- ssh/agent-launchers/output.txt
        +++ /tmp/TestAcceptsshagent-launchers3951038022/002/output.txt
        @@ -4,7 +4,7 @@
         === SetEnv appends the launcher directory to PATH
         SetEnv
         HOME="[TEST_TMP_DIR]/server-home"
        -PATH="/usr/bin:/bin:[TEST_TMP_DIR]/server-home/.agent-shim/bin"
        +PATH="/usr/bin:/bin"
         DATABRICKS_HOST="[DATABRICKS_URL]"
         DATABRICKS_TOKEN="[DATABRICKS_TOKEN]"
         DATABRICKS_CLI_PATH="[CLI]"
        @@ -20,16 +20,6 @@
         === Each supported agent has a launcher that delegates to the agent shim
         
         >>> ls server-home/.agent-shim/bin
        -claude
        -codex
        +ls: cannot access 'server-home/.agent-shim/bin': No such file or directory
         
        ->>> cat server-home/.agent-shim/bin/claude
        -#!/usr/bin/env bash
        -# "claude" launcher from "databricks ssh connect"; delegates to "ssh agent-shim claude".
        -export DATABRICKS_WORKSPACE_HOME='/Workspace/Users/[USERNAME]'
        -exec [CLI] ssh agent-shim claude "$@"
        -
        -=== A non-interactive session finds the launchers on PATH
        -/usr/bin:/bin:[TEST_TMP_DIR]/server-home/.agent-shim/bin
        -[TEST_TMP_DIR]/server-home/.agent-shim/bin/claude
        -[TEST_TMP_DIR]/server-home/.agent-shim/bin/codex
        +Exit code: 2
        
    acceptance_test.go:1118: LOG.server: Warn: Failed to create /run/sshd directory, SSHD may not work properly
FAIL

latest (v1.22.1): same output as main (270b2d0)

@rclarey
rclarey requested review from anton-107 and rugpanov October 26, 2026 12:14
@rclarey
rclarey enabled auto-merge October 26, 2026 13:54

@anton-107 anton-107 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving one finding inline. I consider it a blocker for merging this PR.

Comment thread experimental/ssh/internal/client/agentshim.go Outdated
// involved. Appended (not prepended) so the launchers never shadow a real
// binary of the same name earlier on PATH.
if opts.Serverless && parts[0] == "PATH" {
value += ":" + path.Join(homeDir, agentlauncher.BinDir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] Preserve the launcher directory through IDE login-shell initialization.

SetEnv adds the directory to the initial SSH environment, but Cursor resolves the remote shell environment with -i -l -c and merges the resulting PATH into the environment used by extension hosts and terminals that inherit the shell environment. On serverless, /etc/profile rebuilds PATH from scratch—the existing comment in client.go:955–961 documents this exact behavior. The seeded .bashrc restores only the Python directory, so the agent launcher directory is lost. Adding it only here therefore leaves the advertised --ide support incomplete.

To reproduce on fresh serverless compute, compare:

databricks ssh connect --name pr7123 -- 'command -v claude codex'
databricks ssh connect --name pr7123 -- 'bash -ilc "command -v claude codex"'

The direct command should find both launchers; the login-shell invocation should lose them. Also connect with --ide=cursor and check command -v claude codex in a new terminal. Please ensure the launcher directory is restored after login-profile processing as well as supplied by SetEnv.

Verification: the affected unit tests and the new local SSH acceptance test pass. I inspected Cursor's remote environment-resolution code, but have not run this reproduction on live serverless compute. The new acceptance test avoids login startup files, so it does not cover this path.

@rclarey rclarey Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anton-107

On serverless, /etc/profile rebuilds PATH from scratch—the existing comment in client.go:955–961 documents this exact behavior.

This is not true in my testing. It seems that comment is out of date or incorrect

databricks ssh connect --name pr7123 -- 'bash -ilc "command -v claude codex"'

This correctly finds claude and codex for me. Cursor as well works as expected for me

@rclarey
rclarey requested a review from anton-107 October 26, 2026 16:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants