gittcltcsh

Tcl vs Shell: Different Exit Codes for git checkout main Command


I'm getting different exit codes when using catch to execute git checkout main in Tcl compared to running it directly in the shell. In Tcl, when I do % catch "exec git checkout main" result and % catch {exec git checkout main} result, both return 1 as the exit code. But when I run $git checkout main in the shell, and then $echo $?, it returns 0. Also, in Tcl, % puts $result shows "Already on 'main'". Why is there this difference in exit codes and how can I get consistent behavior?

Tcl:

% catch "exec git checkout main" result
1
% catch {exec git checkout main} result
1
% puts $result
Already on 'main'
% exit

tcsh:

$git checkout main
Already on 'main'
$echo $?
0

Solution

  • The Tcl catch command does not return the exit code of the subprocess you invoke using the exec command. It returns the status of the exec command, which is something different.

    The exec command returns an error (code 1) if any of the commands in the pipeline exit abnormally or are killed or suspended. If any of the commands writes to its standard error file and that standard error is not redirected and the -ignorestderr option is not specified, then exec also returns an error. In your case exec returns an error because git reported "Already on 'main'" on standard error.

    To check the exit code of the subprocess executed by exec, you have to look at the errorCode variable, or the -errorcode return option:

    catch {exec git checkout main} result options
    puts [dict get $options -errorcode]
    

    When the exit code is 0, this will print "NONE". Otherwise it prints something like "CHILDSTATUS 7230 1", where 7230 is the PID of the subprocess and 1 is the exit code.

    Alternatively, you can act on the error code prefix using the try command:

    try {
        exec git checkout main
    } trap {CHILDSTATUS} {message options} {
        puts "Exit code: [lindex [dict get $options -errorcode] 2]"
    } trap {NONE} {
        # Process wrote to stderr - ignore
    }
    

    To just get a simple pass/fail indication from catch, tell exec to ignore standard error in one of two ways:

    catch {exec git checkout main 2>@1} result
    

    Or

    catch {exec -ignorestderr git checkout main} result
    

    The second method will result in any standard error output of the pipeline to be reported on the standard output of your Tcl program.