@@ -10,6 +10,7 @@ import (
1010 "regexp"
1111 "sort"
1212 "strings"
13+ "time"
1314
1415 "github.com/traefik/yaegi/interp"
1516 "github.com/traefik/yaegi/stdlib"
@@ -250,6 +251,15 @@ func (c *CodeModeUTCP) Execute(ctx context.Context, args CodeModeArgs) (CodeMode
250251 return c .executeFunc (ctx , args )
251252 }
252253
254+ // 1. Enforce Timeout via Context
255+ // Convert integer ms to Duration. Default to 30s if invalid.
256+ timeoutMs := args .Timeout
257+ if timeoutMs <= 0 {
258+ timeoutMs = 30000
259+ }
260+ ctx , cancel := context .WithTimeout (ctx , time .Duration (timeoutMs )* time .Millisecond )
261+ defer cancel ()
262+
253263 i , stdout , stderr := newInterpreter ()
254264
255265 if err := injectHelpers (i , c .client ); err != nil {
@@ -261,20 +271,69 @@ func (c *CodeModeUTCP) Execute(ctx context.Context, args CodeModeArgs) (CodeMode
261271 return CodeModeResult {}, fmt .Errorf ("failed to prepare program: %w" , err )
262272 }
263273
264- if _ , err := i .Eval (wrapped ); err != nil {
265- return CodeModeResult {}, fmt .Errorf ("code execution failed: %w\n stdout: %s\n stderr: %s" , err , stdout .String (), stderr .String ())
274+ // 2. Structure for async result handling
275+ type evalResult struct {
276+ val reflect.Value
277+ err error
266278 }
279+ done := make (chan evalResult , 1 )
280+
281+ // 3. Run Eval in a Goroutine
282+ go func () {
283+ // Safety: recover from internal interpreter panics
284+ defer func () {
285+ if r := recover (); r != nil {
286+ done <- evalResult {err : fmt .Errorf ("interpreter panic: %v" , r )}
287+ }
288+ }()
289+
290+ // Phase A: Compilation & Definition
291+ if _ , err := i .Eval (wrapped ); err != nil {
292+ done <- evalResult {err : fmt .Errorf ("compilation failed: %w" , err )}
293+ return
294+ }
267295
268- v , err := i .Eval (`main.run()` )
269- if err != nil {
270- return CodeModeResult {}, fmt .Errorf ("failed to get return value: %w\n %s" , err , stderr .String ())
271- }
296+ // Phase B: Execution of the wrapped runner
297+ v , err := i .Eval (`main.run()` )
298+ done <- evalResult {val : v , err : err }
299+ }()
300+
301+ // 4. Wait for Completion or Timeout
302+ select {
303+ case <- ctx .Done ():
304+ return CodeModeResult {
305+ Stdout : stdout .String (),
306+ Stderr : stderr .String (),
307+ }, fmt .Errorf ("execution timed out after %dms" , timeoutMs )
308+
309+ case res := <- done :
310+ if res .err != nil {
311+ return CodeModeResult {
312+ Stdout : stdout .String (),
313+ Stderr : stderr .String (),
314+ }, fmt .Errorf ("runtime error: %w\n stdout: %s\n stderr: %s" , res .err , stdout .String (), stderr .String ())
315+ }
272316
273- return CodeModeResult {
274- Value : v .Interface (),
275- Stdout : stdout .String (),
276- Stderr : stderr .String (),
277- }, nil
317+ // 5. Handle Result & Check for Error Objects
318+ finalVal := res .val .Interface ()
319+ finalStderr := stderr .String ()
320+
321+ // FIX: If the user code returned an `error` type (e.g. "return err"),
322+ // we capture it and move it to Stderr so the Tool Handler treats it as a failure.
323+ if errObj , ok := finalVal .(error ); ok {
324+ if finalStderr != "" {
325+ finalStderr += "\n "
326+ }
327+ finalStderr += "Script returned error: " + errObj .Error ()
328+ finalVal = nil // Nullify value since it was an error
329+ }
330+
331+ return CodeModeResult {
332+ Value : finalVal ,
333+ Stdout : stdout .String (),
334+ Stderr : finalStderr ,
335+ }, nil
336+ }
278337}
279338
280339func (s * codeModeStream ) Next () (any , error ) {
0 commit comments