Skip to content

Commit d675591

Browse files
robstolarzclaude
andcommitted
feat: support job ID as positional arg in depot ci ssh
`depot ci ssh <job-id>` now works — the backend resolves the job ID to its parent run, and findJob matches by job ID when no --job flag is given. This is more convenient than specifying run ID + --job key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b383913 commit d675591

File tree

2 files changed

+27
-13
lines changed

2 files changed

+27
-13
lines changed

pkg/cmd/ci/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ This command is in beta and subject to change.`,
215215

216216
if ssh {
217217
fmt.Printf("Waiting for job to start and connecting via SSH...\n")
218-
sandboxID, sessionID, err := waitForSandbox(ctx, tokenVal, orgID, resp.RunId, jobNames[0])
218+
sandboxID, sessionID, err := waitForSandbox(ctx, tokenVal, orgID, resp.RunId, jobNames[0], "")
219219
if err != nil {
220220
return err
221221
}

pkg/cmd/ci/ssh.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
3435
If the job hasn't started yet, the command will wait for the sandbox to be provisioned.
3536
Use --info to print SSH connection details instead of connecting interactively.
3637
3738
This 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

Comments
 (0)