After the node early-return, narrow workdir back to string via
resolvedWorkdir for gateway/sandbox paths. Update
buildExecApprovalPendingToolResult and buildApprovalPendingMessage
to accept string | undefined for cwd since node execution may omit it.
When exec runs with host=node and no explicit cwd is provided, the
gateway was injecting its own process.cwd() as the default working
directory. In cross-platform setups (e.g. Linux gateway + Windows node),
this gateway-local path does not exist on the node, causing
"SYSTEM_RUN_DENIED: approval requires an existing canonical cwd".
This change detects when no explicit workdir was provided (neither via
the tool call params.workdir nor via agent defaults.cwd) and passes
undefined instead of the gateway cwd. This lets the remote node use its
own default working directory.
Changes:
- bash-tools.exec.ts: Track whether workdir was explicitly provided;
when host=node and no explicit workdir, pass undefined instead of
gateway process.cwd()
- bash-tools.exec-host-node.ts: Accept workdir as string | undefined;
only send cwd to system.run.prepare when defined
- bash-tools.exec-approval-request.ts: Accept workdir as
string | undefined in HostExecApprovalParams
Fixes#58934
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an exec command fails (e.g. timeout), the tool previously rejected
with an Error, which the tool adapter caught and wrapped in a JSON object
({ status, tool, error }). The model then received this raw JSON as the
tool result and could parrot it verbatim to the user.
Now exec failures resolve with a proper tool result containing the error
as human-readable text in content[], matching the success path structure.
The model sees plain text it can naturally incorporate into its reply.
Also fixes a pre-existing format issue in update-cli.test.ts.
Fixes#52484
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(gateway): add register and awaitDecision methods to ExecApprovalManager
Separates registration (synchronous) from waiting (async) to allow callers
to confirm registration before the decision is made. Adds grace period for
resolved entries to prevent race conditions.
* feat(gateway): add two-phase response and waitDecision handler for exec approvals
Send immediate 'accepted' response after registration so callers can confirm
the approval ID is valid. Add exec.approval.waitDecision endpoint to wait for
decision on already-registered approvals.
* fix(exec): await approval registration before returning approval-pending
Ensures the approval ID is registered in the gateway before the tool returns.
Uses exec.approval.request with expectFinal:false for registration, then
fire-and-forget exec.approval.waitDecision for the decision phase.
Fixes#2402
* test(gateway): update exec-approval test for two-phase response
Add assertion for immediate 'accepted' response before final decision.
* test(exec): update approval-id test mocks for new two-phase flow
Mock both exec.approval.request (registration) and exec.approval.waitDecision
(decision) calls to match the new internal implementation.
* fix(lint): add cause to errors, use generics instead of type assertions
* fix(exec-approval): guard register() against duplicate IDs
* fix: remove unused timeoutMs param, guard register() against duplicates
* fix(exec-approval): throw on duplicate ID, capture entry in closure
* fix: return error on timeout, remove stale test mock branch
* fix: wrap register() in try/catch, make timeout handling consistent
* fix: update snapshot on timeout, make two-phase response opt-in
* fix: extend grace period to 15s, return 'expired' status
* fix: prevent double-resolve after timeout
* fix: make register() idempotent, capture snapshot before await
* fix(gateway): complete two-phase exec approval wiring
* fix: finalize exec approval race fix (openclaw#3357) thanks @ramin-shirali
* fix(protocol): regenerate exec approval request models (openclaw#3357) thanks @ramin-shirali
* fix(test): remove unused callCount in discord threading test
---------
Co-authored-by: rshirali <rshirali@rshirali-haga.local>
Co-authored-by: rshirali <rshirali@rshirali-haga-1.home>
Co-authored-by: Peter Steinberger <steipete@gmail.com>