`runtime-options.buildRuntimeConfigOptionPairs` translated
`AcpSessionRuntimeOptions.timeoutSeconds` into a
`session/set_config_option(configId: "timeout")` pair on every turn. Both the
control plane (`AcpSessionManager.applyManagerRuntimeControls`) and the ACPX
wrapper (`AcpxRuntime.setConfigOption`) sit between that pair and the backend:
- The control plane validates pairs against the backend's advertised
config-option keys and throws `ACP_BACKEND_UNSUPPORTED_CONTROL` for any
pair the backend did not advertise. claude-agent-acp does not advertise a
`timeout` alias.
- The wrapper then forwards remaining pairs to the delegate. The Codex ACP
command was already short-circuited there; every other command, including
claude-agent-acp, fell through.
Net effect on the reporter's scenario:
`sessions_spawn({ runtime:"acp", agentId:"claude", timeoutSeconds: 60 })`
failed at the control-plane validation with `ACP_BACKEND_UNSUPPORTED_CONTROL`
(and, had it reached the wire, claude-agent-acp would have answered
`-32603 Internal error / Unknown config option: timeout`, surfacing as
`ACP_TURN_FAILED: Internal error`).
Fix two layers:
1. Control plane (`src/acp/control-plane/runtime-options.ts`): add
`isTimeoutConfigOptionAdvertised(advertisedConfigOptionKeys)` and gate the
timeout pair on it. When advertised keys are unknown (`undefined` or
empty), keep emitting the pair — this preserves current behavior for
backends that have not produced a capability list yet. When advertised
keys are present but exclude every alias in
`RUNTIME_CONFIG_OPTION_ALIASES.timeoutSeconds`, skip the pair. The
per-turn timeout is still enforced in-process via
`AcpSessionManager.resolveTurnTimeoutMs` in `manager.core.ts`.
2. ACPX wrapper (`extensions/acpx/src/runtime.ts`): hoist the Codex
`timeout` / `timeout_seconds` suppression so it also applies to
claude-agent-acp commands. Add `isClaudeAcpCommand` mirroring
`isCodexAcpCommand` (package spec, binary, generated wrapper script).
This layer is defense in depth — relevant when callers reach the wrapper
without going through `applyManagerRuntimeControls`, or when advertised
keys are not yet known.
Coverage:
- `src/acp/control-plane/runtime-options.test.ts` (new) asserts:
- the timeout pair is omitted when advertised keys exclude every alias,
- the pair is kept when `timeout` or `timeout_seconds` is advertised,
- the pair is kept when advertised keys are unknown,
- model/thinking emission is unaffected.
- `extensions/acpx/src/runtime.test.ts` flips the previous
`forwards timeout config controls for non-Codex ACP agents` test, which
codified the buggy behavior, into a suppression assertion. Adds a
positive `still forwards non-timeout config controls for claude-agent-acp`
test and an `isClaudeAcpCommand` detector test.
Closes#81127