Two latent bugs in `service/gateway/io.rs::spawn_gateway_bot`, exposed
today after a long-overdue gateway rebuild:
1. The permission channel sender was bound as `_perm_tx` (the underscore
prefix signalling "unused") and dropped at function return. The
matrix bot's permission_listener task — which holds `perm_rx` for
its lifetime per story 884 — then saw the channel close immediately
and exited with "perm_rx channel closed" 1s after starting. Net
effect: the listener was effectively absent on every gateway boot,
so non-MCP tool permission requests had no destination at all
(separate from the architectural mismatch that 898 will fix; this
was a strictly worse "listener never even ran" version of the same
problem). Bind as `perm_tx` and `mem::forget` it to keep the
channel open for the gateway's lifetime, mirroring the existing
`shutdown_tx` pattern two lines below.
2. `bot_name` was hardcoded to `"Assistant"`, ignoring
`bot.toml::display_name`. So the gateway's matrix bot announced
itself as "Assistant" and treated user messages addressed to
"Timmy" (the actual configured display_name) as unaddressed,
silently dropping them. `ambient_rooms` and
`permission_timeout_secs` were similarly ignored. Load
`BotConfig::load(config_dir)` and apply the same field plumbing
the standard-mode initialisation in `main.rs:211-232` already
uses.
Symptoms seen in production today:
- gateway.log: `Sending startup announcement: Assistant is online.`
followed by repeated `Ignoring unaddressed message from
@yossarian:crashlabs.io` lines.
- gateway.log: `permission listener started` immediately followed
(same timestamp) by `permission listener exiting (perm_rx channel
closed)`.
After this lands, rebuild the gateway binary and restart so it picks
up `bot.toml` correctly and the listener stays alive for the bot's
lifetime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mergemaster gates run rustfmt and rejected 864's merge because
several files I added/touched in master today had not been fmt'd.
Six files affected, mostly trivial line-wrapping nits. Fixes the
formatting gate for the next 864 merge attempt.
Before: tool_run_check (and run_build/run_lint via run_script_tool)
returned the entire cargo log verbatim in `output`. For runs with many
errors the response routinely exceeded the MCP token cap, was dumped
to a tool-results file, and the agent had to scrape it with python3
just to see the error list — burning many turns on file archaeology
for what should be a one-look operation. Real example: 864's coder
hit `result (143,708 characters) exceeds maximum allowed tokens` and
spent ~8 turns extracting 3 errors.
Now:
- New `service::shell::parse_diagnostics` parses `error[CODE]:` /
`warning[CODE]:` headers + their `--> file:line` markers into
structured `Diagnostic { kind, code, message, file, line }`.
- `tool_run_check` (and the run_build/run_lint shared body) returns
`{ passed, exit_code, errors: [...], warnings: [...], summary }`.
Raw `output` is dropped from the default response.
- New `verbose: bool` argument (default false) restores the raw
output for callers who actually need it.
- Updated the existing tool_run_check test to assert the new
contract (150 errors → 150 structured entries, response < 50KB).
Skipped run_tests in this pass — its parser would need to recognise
test-runner output (different format from cargo); will land separately.
Closes 886.
The 861-line diagnostics.rs is split:
- permission.rs: tool_prompt_permission + helpers + their tests (584 lines)
- usage.rs: tool_get_token_usage + tests (122 lines)
- mod.rs: server_logs, rebuild, version, loc_file, dump_crdt, move_story + tests (185 lines)
Tests stay co-located. The bigger sub-modules (permission at 584 with tests
mostly under 800; usage at 122) are well within the 800-line guide.
Also added #[allow(unused_imports)] to two now-pedantic re-exports in
service/diagnostics/mod.rs that the split made flag.
All 2636 tests pass; clippy clean.