Skip to content

Commit 4356854

Browse files
committed
fix(inspector): kill demo emitter — show real data only or "(no events)"
User asked twice to kill the demo mock data and I left a fallback in place that kept silently activating because of subtle is_terminal() discrepancies between default_command (stdin-tty check) and app::run (separate stdin-tty check). The cockpit displayed synthetic hydra.veto.fired / sylph.destructive.veto / shell.exec events that literally do not exist in the real JSONL hook stream — they were coming from src/demo.rs's synthetic emitter, masking real-data issues. Fix: remove the demo-mode branch from app::run entirely. If Source::Stdin is reached with no piped data, the cockpit shows "(no events)" honestly. No more silent fallback to synthetic animation. Also: lib.rs gains an eprintln! at startup naming the resolved hook-jsonl path + tail-vs-no-hooks decision so users can confirm which mode the cockpit picked without grepping the tracing log. src/demo.rs stays in the codebase as legacy code (unused) — could be revived for tests / fixtures but is no longer wired into the default user-facing flow.
1 parent 55ef0d8 commit 4356854

2 files changed

Lines changed: 33 additions & 31 deletions

File tree

inspector/src/app.rs

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,27 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
3939
let terminal = &mut guard.terminal;
4040

4141
// Demo-mode trigger: stdin source AND no pipe (terminal stdin) → skip
42-
// the real transport and run the built-in synthetic emitter so the
43-
// dashboard always shows life on a bare `enchanter.exe` launch.
44-
let demo_mode = matches!(config.source, Source::Stdin) && io::stdin().is_terminal();
45-
46-
// Build the unified event receiver — either from real transport or demo.
47-
// The control writer is connected ONLY for `Source::SocketControl`; every
48-
// other source path returns a disconnected writer so `send_control` errors
49-
// visibly when the user tries to approve/veto on a read-only stream.
50-
let (mut event_rx, control_writer): (mpsc::Receiver<Event>, ControlWriter) = if demo_mode {
51-
let (tx, rx) = mpsc::channel::<Event>(1024);
52-
crate::demo::spawn_demo_emitter(tx);
53-
(rx, ControlWriter::disconnected())
54-
} else {
55-
match config.source.clone() {
56-
Source::SocketControl(_) => {
57-
// try_spawn opens the socket eagerly so we surface failure
58-
// before the TUI swallows the error.
59-
let transport = Transport::try_spawn(map_source(config.source), 1024).await?;
60-
let writer = transport.writer();
61-
(transport.into_receiver(), writer)
62-
}
63-
_ => (
64-
Transport::spawn(map_source(config.source), 1024).into_receiver(),
65-
ControlWriter::disconnected(),
66-
),
42+
// Demo mode REMOVED. Earlier versions auto-launched the synthetic
43+
// emitter when stdin was a TTY + Source::Stdin — that path was masking
44+
// real-data issues by silently filling the cockpit with fake events.
45+
// The user explicitly asked to kill it. If a Source::Stdin is reached
46+
// here with no piped input, the cockpit will simply sit at "(no events)"
47+
// until the user pipes something in or quits — honest, non-deceiving.
48+
let demo_mode = false;
49+
50+
let (mut event_rx, control_writer): (mpsc::Receiver<Event>, ControlWriter) = match config
51+
.source
52+
.clone()
53+
{
54+
Source::SocketControl(_) => {
55+
let transport = Transport::try_spawn(map_source(config.source), 1024).await?;
56+
let writer = transport.writer();
57+
(transport.into_receiver(), writer)
6758
}
59+
_ => (
60+
Transport::spawn(map_source(config.source), 1024).into_receiver(),
61+
ControlWriter::disconnected(),
62+
),
6863
};
6964

7065
// Keyboard: poll-on-blocking-thread, forward Crossterm events over a channel.

inspector/src/lib.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ fn claude_code_hook_jsonl() -> PathBuf {
184184
/// no longer routes there.
185185
fn default_command() -> Command {
186186
use std::io::IsTerminal;
187-
if !std::io::stdin().is_terminal() {
187+
let stdin_tty = std::io::stdin().is_terminal();
188+
if !stdin_tty {
189+
eprintln!("[enchanter] stdin is not a TTY → reading JSONL from stdin (pipe mode)");
188190
return Command::Inspect(InspectArgs::default());
189191
}
190192

@@ -193,12 +195,17 @@ fn default_command() -> Command {
193195
// since install), but the parent dir does, and `--tail` waits up to 30s
194196
// for the file to appear. That's the "boom, real data" UX.
195197
let hook_jsonl = claude_code_hook_jsonl();
196-
let hooks_wired_up = hook_jsonl.exists()
197-
|| hook_jsonl.parent().map(|p| p.is_dir()).unwrap_or(false);
198+
let exists = hook_jsonl.exists();
199+
let parent_exists = hook_jsonl.parent().map(|p| p.is_dir()).unwrap_or(false);
200+
let hooks_wired_up = exists || parent_exists;
201+
eprintln!(
202+
"[enchanter] checking hooks: path={} exists={} parent_dir={} → {}",
203+
hook_jsonl.display(),
204+
exists,
205+
parent_exists,
206+
if hooks_wired_up { "TAIL" } else { "no-hooks" }
207+
);
198208
if hooks_wired_up {
199-
// Log so users can grep ~/.cache/enchanter/inspector.log to confirm
200-
// which mode bare `enchanter` picked — useful when troubleshooting
201-
// "is this real or showcase data" without restarting the binary.
202209
tracing::info!(
203210
path = %hook_jsonl.display(),
204211
"default_command: hooks wired up — tailing real Claude Code stream"

0 commit comments

Comments
 (0)