On macOS, terminal emulators may report Cmd (SUPER) instead of Ctrl
(CONTROL) for keyboard shortcuts, depending on the terminal app and
its configuration. This caused Ctrl+B, Ctrl+Alt+2, and other shortcuts
to be inconsistent.
Fix:
- Add normalize_macos_modifiers() in composer_ui.rs
- On macOS: map SUPER to CONTROL when CONTROL is not already set
- On other platforms: no-op
- Apply normalization at the key event entry point in ui.rs
Tests:
- normalize_macos_modifiers_maps_super_to_control
- normalize_macos_modifiers_preserves_existing_control
- normalize_macos_modifiers_leaves_alt_unchanged
The 1ms heartbeat timeout raced the synchronous touch()->cleanup()
gap on loaded CI runners (Windows scheduler can deschedule >1ms),
intermittently reaping the just-touched agent so cleanup() returned 1.
Widen the timeout to 50ms and the staleness sleep to 150ms to keep the
logic exercised without the timing race. Addresses CI flakiness under
the v0.9.0 stabilization gate (#2721).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harvested from PR #2885 by @greyfreedom. Wires ask-rules into the
app-server and core ExecPolicyEngine (previously inert). Removes the
original PR's NeedsApproval arm that incorrectly allow-listed the
working directory as a network host.
Co-Authored-By: greyfreedom <11493871+greyfreedom@users.noreply.github.com>
Harvests 7 safe fixes from PR #2880 by @HUQIANTAO: tool-name hex-digit
guard, token-usage u32 clamp, read-file line usize::try_from, grep
context-lines cap, UTF-8 PDF trim, run_skill dedup, and
Volcengine/SiliconflowCn reasoning_content support. Excludes the
DeepSeek stream-stop change and the unwired prompt_persist module
(deferred for separate review).
Co-Authored-By: HUQIANTAO <58421104+HUQIANTAO@users.noreply.github.com>
Mechanical rustfmt of the runtime_prompt tests rewritten in PR #2874
(LeoAlex0). No logic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tokio::task::spawn_blocking requires a running tokio runtime, which
breaks tests that call hook functions outside a tokio context. Since
hooks are fire-and-forget (no JoinHandle needed), std::thread::spawn
is the correct choice.
Replace platform-specific std::os::unix::io::FromRawFd with
Box<dyn std::io::Write + Send> return type. This compiles on
Windows, macOS, and Linux without unsafe code.
The closure now returns a boxed writer that is either:
- The cloned file handle (success case)
- A reopened file handle (clone failed)
- stderr (last resort, prevents panic)
1. Replace let-else with if-let-Some to avoid compilation error
- let-else with return would return from the entire function
- if-let-Some correctly assigns to tool_registry and continues
2. Preserve original goal_objective_for_prompt behavior
- Return None (not fallback) when objective exists but goal is inactive
- Use state.is_active().then() to match original semantics
1. Fix Mutex lock().unwrap() in MCP server (mcp_server.rs:384,434)
- Use unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks
- Previously, a single panic while holding the lock would cascade to all threads
2. Fix std::thread::spawn in async code (hooks.rs:1055)
- Replace std::thread::spawn with tokio::task::spawn_blocking
- Respects tokio's thread pool limits instead of creating unbounded OS threads
- Fire-and-forget hook execution now properly managed by tokio runtime
3. Fix dropped JoinHandle in SSE loop (mcp.rs:647)
- Store the JoinHandle in SseTransport struct
- Enables detection of SSE loop termination
- Prevents silent connection loss without structured error reporting
4. Fix std::sync::Mutex poison handling in cost_status (cost_status.rs:28-58)
- Use unwrap_or_else(|e| e.into_inner()) to recover from poisoned locks
- Previously, a panic while holding the lock silently lost all subsequent cost data
- Cost tracking now survives mutex poisoning
5. Fix .expect() in tracing writer (runtime_log.rs:162)
- Replace expect() with fallback chain: try_clone -> reopen file -> stderr
- Prevents panicking inside tracing subscriber on fd exhaustion
- Previously, EMFILE during logging would crash the application
1. Fix deny rule prefix matching without word boundary (execpolicy/lib.rs:351-353)
- Deny rule 'rm' now blocks 'rm -rf /' but NOT 'rmdir' or 'rmview'
- Previously used bare starts_with which matched any command starting with 'rm'
- Add word-boundary check: command must equal rule or start with rule+space
2. Fix fallback prefix match clarity (execpolicy/bash_arity.rs:362-374)
- Improve comment to clarify word-boundary matching behavior
- The trailing space in starts_with already provides word boundary
3. Fix hardcoded AskForApproval::OnRequest in HTTP API (app-server/lib.rs:283)
- Read approval_policy from config instead of hardcoding OnRequest
- Users with 'auto'/'yolo' policy now get UnlessTrusted for API calls
- Previously ignored user's configured security posture
4. Fix fuzzy indentation search destroying preceding text (tools/file.rs:714-735)
- When match starts mid-line after whitespace stripping, use exact position
- Previously always expanded to line start, destroying preceding content
- Now only expands to line start when match is at a line boundary
5. Fix potential underflow in apply_hunk start index (tools/apply_patch.rs:1110-1115)
- Use checked_add_signed to safely handle negative cumulative_offset
- Prevents isize overflow on adversarial patch input
- Clamp to lines.len() instead of relying on .max(0) cast
1. Fix swallowed persist_config errors (app-server/lib.rs:882,896)
- Log errors when config persistence fails after set/unset
- Users previously got success response even when disk write failed
2. Fix swallowed job store load error (core/lib.rs:751)
- Add warning log when job store fails to load at startup
- Previously silently started with empty job list on corruption
3. Fix silent config parse failures (config/lib.rs:1590)
- Log warning when project config TOML is malformed
- Previously returned None indistinguishable from 'no config file'
4. Fix MCP connect_all errors swallowed (mcp.rs:2151,2189)
- Log warnings for each server that fails to connect
- Previously returned incomplete resource list with no indication
5. Fix error context stripped in engine status (core/engine.rs:2223)
- Use {err:#} format to include full error chain
- Was inconsistent with line 2234 which already used {err:#}
6. Fix tool audit log failures silently dropped (tool_execution.rs:122-136)
- Log each failure: serialization, directory creation, file open, write
- Previously silently dropped all errors for security audit trail
7. Fix Err(_) arms discarding error info (runtime_log.rs:179, runtime_threads.rs:828)
- Log stderr redirect failures on Windows
- Log poisoned mutex in pending_approvals
8. Fix env var parsing errors silently ignored (config/lib.rs:2519-2530)
- Warn when DEEPSEEK_TELEMETRY, DEEPSEEK_YOLO, DEEPSEEK_HTTP_HEADERS
have invalid values instead of silently treating as unset
9. Fix MCP config reload errors swallowed (mcp.rs:2011)
- Log config reload errors instead of complete silence
10. Fix .expect() on sub-agent runtime (core/engine.rs:1715)
- Gracefully fall back to basic tool set when API client missing
- Previously panicked if subagents enabled but no client configured
11. Fix .expect() on goal objective (core/engine.rs:2543)
- Use safe if-let pattern instead of check+expect
- Prevents panic if refactoring changes control flow
- Add runtime_policy_reference_is_included_in_full_prompt test to verify
that render_runtime_policy_reference() output lands in the composed
system prompt. Guards against silent breakage if the push_str() call
is accidentally removed (all existing tests would still pass).
- Strengthen change_mode_op_updates_current_mode_and_emits_status:
destructure SessionUpdated to assert that session messages do NOT
contain <runtime_prompt> tags after mode change — verifying the core
invariant that Op::ChangeMode does not write session history.
- Extend current_mode_field_assignment_takes_effect_synchronously:
now also verifies that messages_with_turn_metadata() produces the
correct runtime tag (mode="yolo" approval="auto") after a mode
switch, covering the tag-generation mechanism end-to-end.
- Fix outdated comments in composed_prompt_no_longer_inlines_tool_taxonomy
and plan_prompt_taxonomy_omits_run_tests: replace stale references to
deleted <mode_prompt> metadata with accurate descriptions of the
## Runtime Policy Reference section.
The test cache_inspect_displays_tool_result_budget_metadata relied on a
writable $HOME/.codewhale/tool_outputs/ for tool-result wire-dedup
persistence. nix build sandboxes have a read-only home tree, so the
first tool-result SHA spillover write failed, the dedup hash table was
never populated, and the second identical tool result was not marked
deduplicated — causing the expect("repeat tool-result sighting should
report dedup metadata") assertion to fail.
Set TEST_SPILLOVER_ROOT to a tempdir inside the test (matching the
with_tool_result_sha_spillover_root pattern in chat.rs), so the
wire-dedup path works in any environment without depending on $HOME.
- Rename mode_change_op_updates_current_mode_and_emits_session_updated
to current_mode_field_assignment_takes_effect_synchronously.
- The test directly mutates engine.current_mode, not through Op::ChangeMode.
The dispatch path is separately covered by
change_mode_op_updates_current_mode_and_emits_status.
- Inline mode_prompt_marker_value and approval_prompt_marker_value into
runtime_prompt_text (each called exactly once).
- Remove default_approval_mode_for_mode — zero callers.
- Replace the 2 remaining test callers with render_core_tool_taxonomy_body
(neither test depends on the ## heading — they check content only).
- Delete render_core_tool_taxonomy_block — zero production callers after
the previous refactor.
- Add render_core_tool_taxonomy_body(mode) that generates the tool
taxonomy text without the ## Core Tool Taxonomy heading.
- Refactor render_core_tool_taxonomy_block to use the body function
internally (DRY).
- Delete taxonomy_body() — a downstream strip_prefix hack that
worked around the source format instead of fixing it.
- Also removes the now-unnecessary debug_assert! (over-defensive,
since the two functions are co-located in the same file).
- Add proper blank lines (\n\n) before mode headings in
render_runtime_policy_reference (CommonMark/GFM compliance).
- Demote subheadings in agent.md from ##### to ###### so they
nest correctly under the demoted main heading.
- Add debug_assert! in taxonomy_body() to loudly fail when
render_core_tool_taxonomy_block format changes, preventing
silent heading-hierarchy breakage.
- Add render_runtime_policy_reference() in prompts.rs containing all
mode and approval policy descriptions in the frozen system-prompt
prefix (sent once per session, cache-hit thereafter).
- Simplify runtime_prompt_text() from ~500-token XML block to a ~16-token
self-closing tag (<runtime_prompt visibility="internal" mode="..." approval="..."/>).
- Fix markdown heading hierarchy in all prompts/modes/*.md and
prompts/approvals/*.md (## → #####) to nest correctly under ####.
- Remove now-unused legacy functions: mode_prompt(),
approval_prompt_for_mode(), mode_change_runtime_message().
- Simplify Op::ChangeMode: no longer persists a mode_change event
(next turn tag carries the current mode).
- Update and rename affected tests.
Builds on #2801. Reduces per-request runtime prompt overhead by 97%
(~471 tokens saved per API call). System prompt grows by ~1325 tokens
in the frozen prefix (one-time miss cost); break-even at 3 API calls.