@@ -27,25 +27,26 @@ func NewCmdSSH() *cobra.Command {
2727 )
2828
2929 cmd := & cobra.Command {
30- Use : "ssh <run-id>" ,
30+ Use : "ssh <run-id | job-id >" ,
3131 Short : "Connect to a running CI job via interactive terminal [beta]" ,
3232 Long : `Open an interactive terminal session to the sandbox running a CI job.
3333
34+ Accepts either a run ID (with optional --job flag) or a job ID directly.
3435If the job hasn't started yet, the command will wait for the sandbox to be provisioned.
3536Use --info to print SSH connection details instead of connecting interactively.
3637
3738This command is in beta and subject to change.` ,
38- Example : ` # Connect to a running job
39+ Example : ` # Connect directly using a job ID
40+ depot ci ssh <job-id>
41+
42+ # Connect to a specific job in a run
3943 depot ci ssh <run-id> --job build
4044
4145 # Auto-select job when there's only one
4246 depot ci ssh <run-id>
4347
4448 # Print SSH connection details (for agents/automation)
45- depot ci ssh <run-id> --job build --info
46-
47- # Print SSH details as JSON
48- depot ci ssh <run-id> --job build --info --output json` ,
49+ depot ci ssh <run-id> --info --output json` ,
4950 RunE : func (cmd * cobra.Command , args []string ) error {
5051 if len (args ) == 0 {
5152 return cmd .Help ()
@@ -66,7 +67,7 @@ This command is in beta and subject to change.`,
6667 return fmt .Errorf ("missing API token, please run `depot login`" )
6768 }
6869
69- sandboxID , sessionID , err := waitForSandbox (ctx , tokenVal , orgID , runID , job )
70+ sandboxID , sessionID , err := waitForSandbox (ctx , tokenVal , orgID , runID , job , runID )
7071 if err != nil {
7172 return err
7273 }
@@ -94,7 +95,9 @@ This command is in beta and subject to change.`,
9495
9596// waitForSandbox polls the CI run status until a sandbox_id is available for the
9697// target job, or returns an error if the job has finished or doesn't exist.
97- func waitForSandbox (ctx context.Context , token , orgID , runID , jobKey string ) (sandboxID , sessionID string , err error ) {
98+ // originalID is the raw ID the user passed — it may be a run ID or a job ID.
99+ // When jobKey is empty, we also try matching jobs by ID using originalID.
100+ func waitForSandbox (ctx context.Context , token , orgID , runID , jobKey , originalID string ) (sandboxID , sessionID string , err error ) {
98101 const pollInterval = 2 * time .Second
99102 const timeout = 5 * time .Minute
100103
@@ -118,7 +121,7 @@ func waitForSandbox(ctx context.Context, token, orgID, runID, jobKey string) (sa
118121 return "" , "" , fmt .Errorf ("failed to get run status: %w" , err )
119122 }
120123
121- targetJob , err := findJob (resp , jobKey )
124+ targetJob , err := findJob (resp , jobKey , originalID )
122125 if err == nil && jobKey == "" {
123126 // Latch the auto-selected job key so subsequent polls don't
124127 // fail if more jobs appear while we wait for the sandbox.
@@ -188,9 +191,9 @@ func isRetryableJobError(err error) bool {
188191}
189192
190193// findJob locates the target job in the run status response.
191- // If jobKey is empty and there's exactly one job, it returns that job.
192- // If there are multiple jobs and no jobKey, it returns an error listing them .
193- func findJob (resp * civ1.GetRunStatusResponse , jobKey string ) (* civ1.JobStatus , error ) {
194+ // It tries matching by job key (-- job flag), then by job ID (originalID),
195+ // then auto-selects if there's exactly one job .
196+ func findJob (resp * civ1.GetRunStatusResponse , jobKey , originalID string ) (* civ1.JobStatus , error ) {
194197 var allJobs []* civ1.JobStatus
195198 for _ , wf := range resp .Workflows {
196199 allJobs = append (allJobs , wf .Jobs ... )
@@ -200,6 +203,7 @@ func findJob(resp *civ1.GetRunStatusResponse, jobKey string) (*civ1.JobStatus, e
200203 return nil , & retryableJobError {msg : fmt .Sprintf ("run %s has no jobs yet" , resp .RunId )}
201204 }
202205
206+ // Match by job key (--job flag).
203207 if jobKey != "" {
204208 for _ , j := range allJobs {
205209 if j .JobKey == jobKey {
@@ -210,6 +214,16 @@ func findJob(resp *civ1.GetRunStatusResponse, jobKey string) (*civ1.JobStatus, e
210214 return nil , & retryableJobError {msg : fmt .Sprintf ("job %q not found yet" , jobKey )}
211215 }
212216
217+ // Match by job ID (user passed a job ID as the positional arg).
218+ if originalID != "" {
219+ for _ , j := range allJobs {
220+ if j .JobId == originalID {
221+ return j , nil
222+ }
223+ }
224+ }
225+
226+ // Auto-select if there's only one job.
213227 if len (allJobs ) == 1 {
214228 return allJobs [0 ], nil
215229 }
0 commit comments