Skip to content

Commit e359efb

Browse files
committed
feat(execd): extract exit_code for command error output
1 parent 07eabf5 commit e359efb

10 files changed

Lines changed: 56 additions & 36 deletions

File tree

components/execd/pkg/jupyter/execute/types.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ type ErrorOutput struct {
206206
// EValue is the value of the error
207207
EValue string `json:"evalue"`
208208

209+
// ExitCode is the exit code of the command
210+
ExitCode int `json:"exit_code"`
211+
209212
// Traceback is the traceback of the error
210213
Traceback []string `json:"traceback"`
211214
}
@@ -214,8 +217,9 @@ func (e *ErrorOutput) String() string {
214217
return fmt.Sprintf(`
215218
Error: %s
216219
Value: %s
220+
ExitCode: %d
217221
Traceback: %s
218-
`, e.EName, e.EValue, strings.Join(e.Traceback, "\n"))
222+
`, e.EName, e.EValue, e.ExitCode, strings.Join(e.Traceback, "\n"))
219223
}
220224

221225
// StatusUpdate represents kernel status update

components/execd/pkg/runtime/bash_session.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,14 @@ func (s *bashSession) run(ctx context.Context, request *ExecuteCodeRequest) erro
287287
}
288288
if request.Hooks.OnExecuteError != nil {
289289
request.Hooks.OnExecuteError(&execute.ErrorOutput{
290-
EName: "CommandExecError",
290+
EName: commandExecError,
291+
// Deprecated: EValue is deprecated for command, use ExitCode instead
291292
EValue: strconv.Itoa(userExitCode),
293+
ExitCode: userExitCode,
292294
Traceback: []string{errMsg},
293295
})
294296
}
295-
log.Error("CommandExecError: %s (command: %q)", errMsg, request.Code)
297+
log.Error("%s: %s (command: %q)", commandExecError, errMsg, request.Code)
296298
return nil
297299
}
298300

components/execd/pkg/runtime/bash_session_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ func TestBashSession_NonZeroExitEmitsError(t *testing.T) {
7575
require.Fail(t, "expected error hook to be called")
7676
}
7777
require.NotNil(t, gotErr, "expected non-nil error output")
78-
require.Equal(t, "CommandExecError", gotErr.EName)
79-
require.Equal(t, "7", gotErr.EValue)
78+
require.Equal(t, commandExecError, gotErr.EName)
79+
require.Equal(t, 7, gotErr.ExitCode)
8080
require.NotEmpty(t, sessionID, "expected session id to be set")
8181
require.Equal(t, "before", stdoutLine)
8282

components/execd/pkg/runtime/command.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
140140
close(done)
141141
wg.Wait()
142142
request.Hooks.OnExecuteInit(session)
143-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "CommandExecError", EValue: err.Error()})
144-
log.Error("CommandExecError: error starting commands: %v", err)
143+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: commandInitError, EValue: err.Error()})
144+
log.Error("%s: error starting commands: %v", commandInitError, err)
145145
return nil
146146
}
147147

@@ -185,23 +185,25 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
185185
var exitError *exec.ExitError
186186
if errors.As(err, &exitError) {
187187
exitCode := exitError.ExitCode()
188-
eName = "CommandExecError"
188+
eName = commandExecError
189189
eValue = strconv.Itoa(exitCode)
190190
eCode = exitCode
191191
} else {
192-
eName = "CommandExecError"
192+
eName = commandExecError
193193
eValue = err.Error()
194194
eCode = 1
195195
}
196196
traceback = []string{err.Error()}
197197

198198
request.Hooks.OnExecuteError(&execute.ErrorOutput{
199-
EName: eName,
199+
EName: eName,
200+
// Deprecated: EValue is deprecated for command, use ExitCode instead
200201
EValue: eValue,
202+
ExitCode: eCode,
201203
Traceback: traceback,
202204
})
203205

204-
log.Error("CommandExecError: error running commands: %v", err)
206+
log.Error("%s: error running commands: %v", commandExecError, err)
205207
c.markCommandFinished(session, eCode, err.Error())
206208
return nil
207209
}
@@ -268,7 +270,7 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
268270
}
269271
if err != nil {
270272
cancel()
271-
log.Error("CommandExecError: error starting commands: %v", err)
273+
log.Error("%s: error starting commands: %v", commandInitError, err)
272274
kernel.running = false
273275
c.storeCommandKernel(session, kernel)
274276
c.markCommandFinished(session, 255, err.Error())
@@ -285,7 +287,7 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
285287
err = cmd.Wait()
286288
cancel()
287289
if err != nil {
288-
log.Error("CommandExecError: error running commands: %v", err)
290+
log.Error("%s: error running commands: %v", commandExecError, err)
289291
exitCode := 1
290292
var exitError *exec.ExitError
291293
if errors.As(err, &exitError) {

components/execd/pkg/runtime/command_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ func TestRunCommand_Error(t *testing.T) {
212212
require.Equal(t, []string{"before"}, stdoutLines)
213213
require.Empty(t, stderrLines, "expected no stderr")
214214
require.NotNil(t, gotErr, "expected error hook to be called")
215-
require.Equal(t, "CommandExecError", gotErr.EName)
216-
require.Equal(t, "3", gotErr.EValue)
215+
require.Equal(t, commandExecError, gotErr.EName)
216+
require.Equal(t, 3, gotErr.ExitCode)
217217
}
218218

219219
// TestStdLogDescriptor_AutoCreatesTempDir verifies that stdLogDescriptor

components/execd/pkg/runtime/command_windows.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
6161

6262
err = cmd.Start()
6363
if err != nil {
64-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "CommandExecError", EValue: err.Error()})
65-
log.Error("CommandExecError: error starting commands: %v", err)
64+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: commandInitError, EValue: err.Error()})
65+
log.Error("%s: error starting commands: %v", commandInitError, err)
6666
return nil
6767
}
6868

@@ -78,25 +78,29 @@ func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest
7878
if err != nil {
7979
var eName, eValue string
8080
var traceback []string
81+
var eCode int
8182

8283
var exitError *exec.ExitError
8384
if errors.As(err, &exitError) {
84-
exitCode := exitError.ExitCode()
85-
eName = "CommandExecError"
86-
eValue = strconv.Itoa(exitCode)
85+
eCode = exitError.ExitCode()
86+
eName = commandExecError
87+
eValue = strconv.Itoa(eCode)
8788
} else {
88-
eName = "CommandExecError"
89+
eName = commandExecError
8990
eValue = err.Error()
91+
eCode = 1
9092
}
9193
traceback = []string{err.Error()}
9294

9395
request.Hooks.OnExecuteError(&execute.ErrorOutput{
94-
EName: eName,
96+
EName: eName,
97+
// Deprecated: EValue is deprecated for command, use ExitCode instead
9598
EValue: eValue,
99+
ExitCode: eCode,
96100
Traceback: traceback,
97101
})
98102

99-
log.Error("CommandExecError: error running commands: %v", err)
103+
log.Error("%s: error running commands: %v", commandExecError, err)
100104
return nil
101105
}
102106
request.Hooks.OnExecuteComplete(time.Since(startAt))
@@ -131,7 +135,7 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
131135
safego.Go(func() {
132136
err := cmd.Start()
133137
if err != nil {
134-
log.Error("CommandExecError: error starting commands: %v", err)
138+
log.Error("%s: error starting commands: %v", commandInitError, err)
135139
pipe.Close() // best-effort
136140
cancel()
137141
return
@@ -161,7 +165,7 @@ func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.Ca
161165
devNull.Close() // best-effort
162166

163167
if err != nil {
164-
log.Error("CommandExecError: error running commands: %v", err)
168+
log.Error("%s: error running commands: %v", commandExecError, err)
165169
exitCode := 1
166170
var exitError *exec.ExitError
167171
if errors.As(err, &exitError) {

components/execd/pkg/runtime/jupyter.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ func (c *Controller) runJupyterCode(ctx context.Context, kernel *jupyterKernel,
119119
}
120120

121121
request.Hooks.OnExecuteError(&execute.ErrorOutput{
122-
EName: "ContextCancelled",
123-
EValue: "Interrupt kernel",
122+
EName: "ContextCancelled",
123+
EValue: "Interrupt kernel",
124+
ExitCode: 255,
124125
})
125126
return errors.New("context cancelled, interrupt kernel")
126127
}

components/execd/pkg/runtime/sql.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ func (c *Controller) runSQL(ctx context.Context, request *ExecuteCodeRequest) er
4343
request.Hooks.OnExecuteInit(uuid.New().String())
4444
err := c.initDB()
4545
if err != nil {
46-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBInitError", EValue: err.Error()})
46+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBInitError", EValue: err.Error(), ExitCode: 255})
4747
log.Error("DBInitError: error initializing db server: %v", err)
4848
return err
4949
}
5050

5151
err = c.db.PingContext(ctx)
5252
if err != nil {
53-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBPingError", EValue: err.Error()})
53+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBPingError", EValue: err.Error(), ExitCode: 255})
5454
log.Error("DBPingError: error pinging db server: %v", err)
5555
return err
5656
}
@@ -69,14 +69,14 @@ func (c *Controller) executeSelectSQLQuery(ctx context.Context, request *Execute
6969

7070
rows, err := c.db.QueryContext(ctx, request.Code)
7171
if err != nil {
72-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error()})
72+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error(), ExitCode: 255})
7373
return nil
7474
}
7575
defer rows.Close()
7676

7777
columns, err := rows.Columns()
7878
if err != nil {
79-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error()})
79+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error(), ExitCode: 255})
8080
return nil
8181
}
8282

@@ -90,7 +90,7 @@ func (c *Controller) executeSelectSQLQuery(ctx context.Context, request *Execute
9090
for rows.Next() {
9191
err := rows.Scan(scanArgs...)
9292
if err != nil {
93-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowScanError", EValue: err.Error()})
93+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowScanError", EValue: err.Error(), ExitCode: 255})
9494
return nil
9595
}
9696
row := make([]any, len(columns))
@@ -104,7 +104,7 @@ func (c *Controller) executeSelectSQLQuery(ctx context.Context, request *Execute
104104
result = append(result, row)
105105
}
106106
if err := rows.Err(); err != nil {
107-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowIterationError", EValue: err.Error()})
107+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowIterationError", EValue: err.Error(), ExitCode: 255})
108108
return nil
109109
}
110110

@@ -114,7 +114,7 @@ func (c *Controller) executeSelectSQLQuery(ctx context.Context, request *Execute
114114
}
115115
bytes, err := json.Marshal(queryResult)
116116
if err != nil {
117-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error()})
117+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error(), ExitCode: 255})
118118
return nil
119119
}
120120
request.Hooks.OnExecuteResult(
@@ -133,7 +133,7 @@ func (c *Controller) executeUpdateSQLQuery(ctx context.Context, request *Execute
133133

134134
result, err := c.db.ExecContext(ctx, request.Code)
135135
if err != nil {
136-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBExecError", EValue: err.Error()})
136+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBExecError", EValue: err.Error(), ExitCode: 255})
137137
return err
138138
}
139139

@@ -144,7 +144,7 @@ func (c *Controller) executeUpdateSQLQuery(ctx context.Context, request *Execute
144144
}
145145
bytes, err := json.Marshal(queryResult)
146146
if err != nil {
147-
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error()})
147+
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error(), ExitCode: 255})
148148
return err
149149
}
150150
request.Hooks.OnExecuteResult(

components/execd/pkg/runtime/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,6 @@ type bashSession struct {
108108
// Set after cmd.Start(), cleared when run() returns. Used by close() to kill the process group.
109109
currentProcessPid int
110110
}
111+
112+
const commandExecError = "CommandExecError"
113+
const commandInitError = "CommandInitError"

specs/execd-api.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,10 @@ components:
10631063
type: string
10641064
description: Error value/message
10651065
example: "name 'undefined_var' is not defined"
1066+
exit_code:
1067+
type: integer
1068+
description: Non-zero exit code if the command has finished
1069+
example: 1
10661070
traceback:
10671071
type: array
10681072
items:

0 commit comments

Comments
 (0)