Skip to content

doctor: promote to a top-level verb and give it runtime evidence - #351

Merged
zzet merged 1 commit into
mainfrom
feat/gortex-doctor-runtime
Jul 26, 2026
Merged

doctor: promote to a top-level verb and give it runtime evidence#351
zzet merged 1 commit into
mainfrom
feat/gortex-doctor-runtime

Conversation

@zzet

@zzet zzet commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Follow-up to #348 (now merged): that PR fixed the Codex install, this one makes the failure it fixed visible.

Why

gortex init doctor was scoped wrong twice over. The name reads repo-local while the report has always been machine-wide. And it only ever described configuration — which is exactly the half that cannot answer the question people run doctor to answer.

An agent config is not evidence. Codex records trust against each hook's hash and skips new or changed hooks until the user approves them in /hooks, so a fully populated config.toml and a completely inert integration are byte-identical on disk. The old doctor would have called the machine in #348's report healthy.

The distinguishing signal already existed and nothing read it: every hook process appends a row to hook-effectiveness.jsonl whether or not it had anything to inject, so an event with zero rows did not run.

gortex audit is a different thing entirely — a repo-level A–F code-health grade from graph topology, for the README badge — so it was not the right home for any of this.

What

gortex doctor is now top-level, with gortex init doctor kept as a deprecated alias. Alongside the adapter drift it always reported:

section question it answers
hook activity did the hook processes run, did they inject, did they reach the daemon?
adoption gortex tool calls vs shell calls, from the agent's own transcripts
savings what the ledger recorded, and how much carries no client or model

Configured-with-zero-runs on a trust-gating agent is a blocker carrying the remedy:

✗ BLOCKER 5 codex hook(s) are configured but none has run in this window —
          codex skips new or changed hooks until they are trusted.
          → run `/hooks` inside Codex, review the gortex entries, and trust them

A single event at zero while others ran is the same finding narrowed to one upgraded hook definition — trust is per-hash, so an upgrade silences exactly the hooks whose definitions changed.

Exit status is non-zero on a blocker so CI can gate on it. --redact hashes repo paths and branch names so the output can be pasted into an issue; --json for machines; --days sets the window.

Structure

  • internal/hooks/telemetry_read.go — the reader lives beside the writer, so the record shape has exactly one definition. A diagnostic that mis-parses its own telemetry is worse than no diagnostic.
  • internal/doctor — transcript scanning and the findings table. Data in, findings out: the whole decision table is testable without a machine that has Codex, hooks, or a daemon on it.
  • codex.Inspect — real TOML parsing that reuses the adapter's own hook recognisers, with a test that installs and then inspects. If Apply's hook shape ever changes without the reader following, that test fails instead of doctor quietly reporting a correct install as unconfigured.

One deliberate omission

The adoption section carries no "did the hook fire" signal. I had planned to look for the SessionStart orientation block in transcripts — then verified Codex 0.145.0 does not persist hook-injected context into rollouts at all. An absent block proves nothing, so the section reports adoption only and says so in its own output, rather than inviting a zero there to be read as "the hook never fired".

Tests

37 new cases across three packages:

  • log reader — window filtering with LastSeen tracked across the whole log (so "last ran 3d ago" survives a narrow window), torn-line tolerance, missing log is not an error.
  • transcript scanner — tool classification (gortex / other-MCP / shell / shell-read), out-of-window and no-tool-call sessions excluded from the ratio, garbage tolerance, row-listing cap that still aggregates everything.
  • findings — never-ran vs ran-but-silent vs daemon-unreachable, trust wording confined to trust-gating agents, unreachable-daemon suppressing the redundant silence warning, shadowed-vs-missing instructions never double-reported, blockers sorting first.

go build ./..., go vet ./..., go test ./internal/... ./cmd/gortex/ — 12,012 tests across 169 packages, green.

Follow-up this exposes

hook-effectiveness.jsonl has no agent dimension, so on a machine running both Claude Code and Codex the counts are the sum and doctor has to caveat itself. The hook command already receives --agent=codex; the log just does not record it. Small change, and it would make this diagnostic unambiguous.

`gortex init doctor` was scoped wrong in its name and in what it could
see. The name reads repo-local while the report has always been
machine-wide, and the report only ever described configuration — which
is exactly the half that cannot answer the question people run doctor to
answer.

An agent config is not evidence. Codex records trust against each hook's
hash and skips new or changed hooks until the user approves them in
/hooks, so a fully populated config.toml and a completely inert
integration are byte-identical on disk. Doctor would have called that
machine healthy. The distinguishing signal already existed and was never
read: every hook process appends a row to hook-effectiveness.jsonl
whether or not it had anything to inject, so an event with zero rows did
not run.

`gortex doctor` now reports, alongside the adapter drift it always did:

  hook activity   runs, injections, and daemon reachability per event.
                  Configured with zero runs on a trust-gating agent is
                  reported as a blocker carrying the /hooks remedy; a
                  single event at zero while others ran is the same
                  finding narrowed to one upgraded hook definition.
  adoption        gortex tool calls vs shell calls from the agent's own
                  session transcripts.
  savings         what the ledger recorded, and how much of it carries
                  no client or model attribution.

Exit status is non-zero when a blocker is found, so CI can gate on it,
and --redact hashes repo paths and branch names so the report can be
pasted into an issue. `gortex init doctor` still works, marked
deprecated.

The adoption section deliberately carries no "did the hook fire" signal:
Codex does not persist hook-injected context into its transcripts, so an
absent orientation block proves nothing and must not be reported as if
it did. Hook liveness comes from the invocation log alone.

Splits the probes into internal/doctor (transcripts, findings) and a
reader in internal/hooks that lives beside the writer, so the record
shape has one definition. codex.Inspect reuses the adapter's own hook
recognisers, and a test installs then inspects, so a writer that changes
shape without the reader following fails rather than silently reporting
a healthy install as unconfigured.
@zzet
zzet changed the base branch from fix/codex-global-instructions-file to main July 26, 2026 07:54
@zzet
zzet merged commit 5a4966e into main Jul 26, 2026
1 check passed
@zzet
zzet deleted the feat/gortex-doctor-runtime branch July 27, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant