You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/undo, /redo, and /undo list report "Checkpoints are not enabled. Set [tools.shell] checkpoints_enabled = true in config." even when that config key genuinely is true and a prior shell write really did execute successfully. This reproduces in the plain default production wiring, not just under capability_scopes/shadow_sentinel gating (unlike #5899/#5905/#5906, which is a different, already-fixed defect in the decorator wrapper types).
Root cause: crates/zeph-tools/src/shell/mod.rs has two separate ToolExecutor impl blocks for the shell executor:
impl ToolExecutor for std::sync::Arc<ShellExecutor> (~line 1732) — a separate, older impl block (predates the checkpoint feature, added for TUI-metrics/risk-chain Arc-sharing in PR refactor(tui): decompose long functions and surface shell background elapsed time #3468, 2026-04-26) that only overrides execute/tool_definitions/execute_tool_call/set_skill_env. It does not override the three checkpoint methods, so they silently fall through to the ToolExecutor trait default (supported: false).
src/agent_setup.rs:593 unconditionally does let shell_executor = Arc::new(shell_executor);before passing it into build_base_executor_chain (line 596) — the same helper used by the CLI, ACP, and daemon entry points (src/runner.rs). Because Rust resolves trait impls on the exact concrete type, every checkpoint call against the shell slot in the composite executor tree resolves to Arc<ShellExecutor>'s incomplete impl, not ShellExecutor's real one. CompositeExecutor::checkpoint_undo/redo/list (crates/zeph-tools/src/composite.rs:111-136) then correctly treats this as "not supported" and falls through to the other branch, but nothing else in the tree supports checkpoints either, so supported stays false all the way up to Agent::handle_undo/handle_redo (crates/zeph-core/src/agent/agent_access_impl.rs:1693,1730,1749).
Note that execute_tool_call (the actual write dispatch) IS forwarded correctly by the Arc<ShellExecutor> impl, so shell writes really do execute and checkpoints really do get recorded internally — it is purely the read-side (/undo, /redo, /undo list) that is severed from the real ShellExecutor instance holding the checkpoint stack.
This is the same defect class as #5899/#5905/#5906 (a new cross-cutting ToolExecutor method added to the leaf type without updating every delegation point that wraps it) but at a different, deeper location — the leaf's own Arc<T> impl block, not a decorator/wrapper type.
Why this was never caught
Every prior CI cycle's coverage-status.md notes for the /undo//redo row (#4990/#5029, CI-1022 through the pre-CI dev session that produced PR #5930) explicitly says "Full TTY e2e still pending" — unit tests exercise ShellExecutor's checkpoint_stack directly, and the wrapper-delegation regression tests added by PR #5930 use local stub inner executors, not a real Arc<ShellExecutor>. None of that coverage constructs an Arc<ShellExecutor> and calls checkpoint methods on it as a dyn ToolExecutor/ErasedToolExecutor trait object, so this gap was invisible to the existing test suite.
Reproduction Steps
Use any config with [tools.shell] checkpoints_enabled = true (capability_scopes/shadow_sentinel not required — reproduces in plain default wiring too, confirmed via static trace of src/agent_setup.rs/src/runner.rs; live-reproduced this cycle with both enabled).
Start zeph (CLI), ask the agent to run a shell command that writes a file, e.g. via the bash tool: echo hello > somefile.txt. Confirm the file is created.
Run /undo list.
Observe: "Checkpoints are not enabled. Set [tools.shell] checkpoints_enabled = true in config." — even though the config key is true and the write just succeeded.
Expected Behavior
/undo list should show the just-recorded checkpoint entry; /undo should revert the write.
Add checkpoint_undo/checkpoint_redo/checkpoint_list overrides to impl ToolExecutor for std::sync::Arc<ShellExecutor> (crates/zeph-tools/src/shell/mod.rs:1732), each forwarding via self.as_ref().checkpoint_X(...), mirroring the file's existing execute_tool_call forwarding pattern. Add a regression test that constructs a real Arc<ShellExecutor> (not a stub), performs a checkpointed write, and calls checkpoint_list/checkpoint_undo through the Arc handle directly, to close this specific coverage gap for good.
Description
/undo,/redo, and/undo listreport"Checkpoints are not enabled. Set [tools.shell] checkpoints_enabled = true in config."even when that config key genuinely istrueand a prior shell write really did execute successfully. This reproduces in the plain default production wiring, not just undercapability_scopes/shadow_sentinelgating (unlike #5899/#5905/#5906, which is a different, already-fixed defect in the decorator wrapper types).Root cause:
crates/zeph-tools/src/shell/mod.rshas two separateToolExecutorimpl blocks for the shell executor:impl ToolExecutor for ShellExecutor(~line 1750) — correctly overridescheckpoint_undo/checkpoint_redo/checkpoint_list, always returningsupported: true.impl ToolExecutor for std::sync::Arc<ShellExecutor>(~line 1732) — a separate, older impl block (predates the checkpoint feature, added for TUI-metrics/risk-chain Arc-sharing in PR refactor(tui): decompose long functions and surface shell background elapsed time #3468, 2026-04-26) that only overridesexecute/tool_definitions/execute_tool_call/set_skill_env. It does not override the three checkpoint methods, so they silently fall through to theToolExecutortrait default (supported: false).src/agent_setup.rs:593unconditionally doeslet shell_executor = Arc::new(shell_executor);before passing it intobuild_base_executor_chain(line 596) — the same helper used by the CLI, ACP, and daemon entry points (src/runner.rs). Because Rust resolves trait impls on the exact concrete type, every checkpoint call against the shell slot in the composite executor tree resolves toArc<ShellExecutor>'s incomplete impl, notShellExecutor's real one.CompositeExecutor::checkpoint_undo/redo/list(crates/zeph-tools/src/composite.rs:111-136) then correctly treats this as "not supported" and falls through to the other branch, but nothing else in the tree supports checkpoints either, sosupportedstaysfalseall the way up toAgent::handle_undo/handle_redo(crates/zeph-core/src/agent/agent_access_impl.rs:1693,1730,1749).Note that
execute_tool_call(the actual write dispatch) IS forwarded correctly by theArc<ShellExecutor>impl, so shell writes really do execute and checkpoints really do get recorded internally — it is purely the read-side (/undo,/redo,/undo list) that is severed from the realShellExecutorinstance holding the checkpoint stack.This is the same defect class as #5899/#5905/#5906 (a new cross-cutting
ToolExecutormethod added to the leaf type without updating every delegation point that wraps it) but at a different, deeper location — the leaf's ownArc<T>impl block, not a decorator/wrapper type.Why this was never caught
Every prior CI cycle's coverage-status.md notes for the
/undo//redorow (#4990/#5029, CI-1022 through the pre-CI dev session that produced PR #5930) explicitly says "Full TTY e2e still pending" — unit tests exerciseShellExecutor's checkpoint_stack directly, and the wrapper-delegation regression tests added by PR #5930 use local stub inner executors, not a realArc<ShellExecutor>. None of that coverage constructs anArc<ShellExecutor>and calls checkpoint methods on it as adyn ToolExecutor/ErasedToolExecutortrait object, so this gap was invisible to the existing test suite.Reproduction Steps
[tools.shell] checkpoints_enabled = true(capability_scopes/shadow_sentinel not required — reproduces in plain default wiring too, confirmed via static trace ofsrc/agent_setup.rs/src/runner.rs; live-reproduced this cycle with both enabled).zeph(CLI), ask the agent to run a shell command that writes a file, e.g. via thebashtool:echo hello > somefile.txt. Confirm the file is created./undo list."Checkpoints are not enabled. Set [tools.shell] checkpoints_enabled = true in config."— even though the config key istrueand the write just succeeded.Expected Behavior
/undo listshould show the just-recorded checkpoint entry;/undoshould revert the write.Environment
zeph-tools(shell/mod.rs,composite.rs),zeph-core(agent/agent_access_impl.rs),src/agent_setup.rsSuggested Fix
Add
checkpoint_undo/checkpoint_redo/checkpoint_listoverrides toimpl ToolExecutor for std::sync::Arc<ShellExecutor>(crates/zeph-tools/src/shell/mod.rs:1732), each forwarding viaself.as_ref().checkpoint_X(...), mirroring the file's existingexecute_tool_callforwarding pattern. Add a regression test that constructs a realArc<ShellExecutor>(not a stub), performs a checkpointed write, and callscheckpoint_list/checkpoint_undothrough theArchandle directly, to close this specific coverage gap for good.