Bug
Inside ( ... ) and { ... ; }, $? does not reflect the previous command in the block. It keeps whatever value it had before the block started, so error-checking patterns silently read the wrong exit code.
Repro (RAM mount, both languages)
$ (false; echo subshell=$?)
mirage: subshell=0
bash: subshell=1
$ { false; echo group=$?; }
mirage: group=0
bash: group=1
Top-level sequences are fine (false; echo $? prints 1): list connections seed session.last_exit_code between commands (python workspace/executor/pipes.py does this in handle_connection at three sites). The subshell body loop (handle_subshell, same file) and the brace-group loop (the COMPOUND branch of workspace/node/execute_node.py) iterate children with execute_node but never assign session.last_exit_code = io.exit_code after each child, so $? expansion (workspace/expand/variable.py reads session.last_exit_code) sees the stale value. TypeScript mirrors the same structure (handleSubshell in workspace/executor/pipes.ts, COMPOUND branch of workspace/node/execute_node.ts) and has the same bug.
Fix shape: set last_exit_code after each child in both loops (and restore the saved value on subshell exit alongside the existing env/cwd restore, since bash subshells do not leak $? changes... actually bash subshells DO propagate their final exit status to the parent's $?, so only the in-loop seeding needs care). Found while writing arithmetic-command integ cases (the cases were rewritten to avoid depending on this).
Bug
Inside
( ... )and{ ... ; },$?does not reflect the previous command in the block. It keeps whatever value it had before the block started, so error-checking patterns silently read the wrong exit code.Repro (RAM mount, both languages)
Top-level sequences are fine (
false; echo $?prints 1): list connections seedsession.last_exit_codebetween commands (pythonworkspace/executor/pipes.pydoes this inhandle_connectionat three sites). The subshell body loop (handle_subshell, same file) and the brace-group loop (the COMPOUND branch ofworkspace/node/execute_node.py) iterate children withexecute_nodebut never assignsession.last_exit_code = io.exit_codeafter each child, so$?expansion (workspace/expand/variable.pyreadssession.last_exit_code) sees the stale value. TypeScript mirrors the same structure (handleSubshellinworkspace/executor/pipes.ts, COMPOUND branch ofworkspace/node/execute_node.ts) and has the same bug.Fix shape: set$? changes... actually bash subshells DO propagate their final exit status to the parent's $ ?, so only the in-loop seeding needs care). Found while writing arithmetic-command integ cases (the cases were rewritten to avoid depending on this).
last_exit_codeafter each child in both loops (and restore the saved value on subshell exit alongside the existing env/cwd restore, since bash subshells do not leak