fix(drone): --json pass-through uses sys.stdout.write, no Rich mid-string wrap (td-49)

--json was routed through Rich console.print(), which defaults to width 80 on
a non-TTY and hard-wraps mid-string, producing invalid JSON (e.g. 'Security
\nScan'). Write raw JSON with sys.stdout.write() in the pass-through paths
(drone.py + router.py); keep Rich for drone's own human UI. Verified live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EccxENcB3KtyT9XuT4ybPz
This commit is contained in:
AIOSAI
2026-07-01 06:12:02 -07:00
co-authored by Claude Opus 4.8
parent 61f958c17e
commit 2a5a370185
4 changed files with 36 additions and 15 deletions
+16
View File
@@ -30,6 +30,22 @@ PyPI version — not the changelog header.
to it). Hooks tests 66 green (presence_gate / cc_sessions / session_boot),
telegram presence_pointer 42 green. (DPLAN-0226 P1/P2, FPLAN-0290/0291/0292)
- **Seedgo stale-template audit checker (`template_check`)** — a new advisory
standard that flags branches still carrying unrendered template markers in
their local prompts / config, so a citizen that never customized its scaffold
no longer fails silently. Auto-discovered like every other checker; advisory
(warns, never blocks). Ships with `template_content.py` and a `template.md`
standard doc, covered by `test_template_check.py`. (built by @seedgo, DPLAN-0228)
### Fixed
- **Drone `--json` output no longer corrupts machine JSON** — `--json`
pass-through was routed through Rich's `console.print()`, which defaults to
width 80 on a non-TTY and hard-wraps mid-string, producing invalid JSON
(e.g. `"Security \nScan"`). Fixed by writing raw JSON with `sys.stdout.write()`
in the pass-through paths (`drone.py` + `router.py`) while keeping Rich for
drone's own human UI. Verified live end-to-end. (fixed by @drone, td-49)
### Changed
- **Presence gate re-sourced to CC-native session files (presence_gate v2)** —
+8 -5
View File
@@ -8,11 +8,14 @@
{
"file": "apps/drone.py",
"standard": "cli",
"lines": [
166,
168
],
"reason": "Raw passthrough of module subprocess stdout/stderr — console.print() breaks routed output"
"lines": [244, 246, 360, 362, 479, 481, 585, 587],
"reason": "Raw passthrough of routed command stdout/stderr — console.print() wraps at 80 cols when piped, corrupting JSON and machine output"
},
{
"file": "apps/modules/router.py",
"standard": "cli",
"lines": [62],
"reason": "Raw passthrough of routed command stdout — console.print() wraps at 80 cols when piped, corrupting JSON and machine output"
},
{
"file": "apps/handlers/scanning/formatters.py",
+8 -8
View File
@@ -241,9 +241,9 @@ def _handle_module(name: str, args: List[str]) -> int:
return 1
if result.get("stdout"):
console.print(result["stdout"], end="", highlight=False, markup=False)
sys.stdout.write(result["stdout"])
if result.get("stderr"):
err_console.print(result["stderr"], end="", highlight=False, markup=False)
sys.stderr.write(result["stderr"])
return result.get("exit_code", 0)
@@ -357,9 +357,9 @@ def _handle_custom_command(args: list[str]) -> int:
return 1
if result.stdout:
console.print(result.stdout, end="", highlight=False)
sys.stdout.write(result.stdout)
if result.stderr:
err_console.print(result.stderr, end="", highlight=False)
sys.stderr.write(result.stderr)
return result.exit_code
@@ -476,9 +476,9 @@ def _handle_target(args: List[str]) -> int:
return 1
if result.stdout:
console.print(result.stdout, end="", highlight=False)
sys.stdout.write(result.stdout)
if result.stderr:
err_console.print(result.stderr, end="", highlight=False)
sys.stderr.write(result.stderr)
return result.exit_code
@@ -582,9 +582,9 @@ def main() -> int:
return 1
if isinstance(result, dict):
if result.get("stdout"):
console.print(result["stdout"], end="", highlight=False)
sys.stdout.write(result["stdout"])
if result.get("stderr"):
err_console.print(result["stderr"], end="", highlight=False)
sys.stderr.write(result["stderr"])
return result.get("exit_code", 0)
return 0 if result else 1
+4 -2
View File
@@ -14,6 +14,7 @@ to the handler layer.
"""
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional
@@ -58,7 +59,7 @@ def handle_command(command: Optional[str] = None, args: Optional[List[str]] = No
cmd_args = args[2:] if len(args) > 2 else None
result = route_command(target, cmd, args=cmd_args)
if result.stdout:
console.print(result.stdout, highlight=False)
sys.stdout.write(result.stdout)
return result.exit_code == 0
if command == "route_all":
if not args:
@@ -132,7 +133,8 @@ def print_introspection():
console.print("[yellow]Connected Handlers:[/yellow]")
console.print(" [cyan]handlers/[/cyan]")
console.print(
" - [cyan]router_handler.py[/cyan] [dim](execute_branch_command — resolves and executes branch commands)[/dim]"
" - [cyan]router_handler.py[/cyan] "
"[dim](execute_branch_command — resolves and executes branch commands)[/dim]"
)
console.print(" - [cyan]executor.py[/cyan] [dim](CommandResult — subprocess execution result dataclass)[/dim]")
console.print()