Skip to content

fix(cli-hygiene): kill-path dedup + pgserve stderr gate (G8 + G9, closes #1677) - #1685

Merged
namastex888 merged 5 commits into
devfrom
fix/cli-hygiene-round-2
May 7, 2026
Merged

namastex888 merged 5 commits into
devfrom
fix/cli-hygiene-round-2

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Summary

Wish .genie/wishes/cli-noise-and-hygiene-cleanup/WISH.md — PR-C wave, post-reviewer-correction.

Two surviving groups from the QA dogfood findings; G3 was dropped (already implemented), G10 stays deferred pending a /trace pass into update.ts:362.

Groups

G8 — genie agent kill dedups shadow + UUID rows in one pass

src/term-commands/agents.ts:handleWorkerKill now atomically clears both halves of the logical agent (dir: shadow ↔ UUID twin, scoped by team). Operators no longer need a second kill to clean the visual zombie that today's dir: kill leaves behind in genie ls.

  • genie agent kill dir:foo removes BOTH the dir:foo shadow AND any UUID row in agents named foo in the same team. — Live: dedup logic in killAgentWithDedup issues a single BEGIN; DELETE dir; DELETE UUIDs; COMMIT;. Test kill dir → both halves removed passes.
  • genie agent kill <uuid-of-foo> removes BOTH the UUID row AND the dir:foo shadow IF no other UUID instances share the name. — Migration 061's idx_agents_custom_name_team unique constraint makes the "no other UUIDs in same team" clause vacuously true; the test kill UUID → dir shadow also removed covers it. Cross-team siblings stay isolated (test cross-team siblings — killing TEAM-A halves leaves TEAM-B UUID intact).
  • Audit log shows exactly one agent.kill.dedup_paired event per dedup-active kill. — recordAuditEvent('agent', w.id, 'kill.dedup_paired', getActor(), { matched, paired }) fires once when the cascade nuked rows.
  • --keep-paired preserves today's single-row behavior. — handleWorkerKill(name, { keepPaired: true }) short-circuits the dedup. genie agent kill <name> --keep-paired exposes it via commander. Tests --keep-paired preserves the dir shadow when killing a UUID and --keep-paired preserves UUID twins when killing the dir shadow both pass.
  • 4-row fixture: kill one logical agent → 2 rows removed; kill the other UUID → 2 more removed; final state is empty. — Test kill dir → both halves removed plus kill UUID → dir shadow also removed cover the 2+2 cascade.

G9 — Reduce [pgserve] connected to postgres stderr noise

src/lib/db.ts:maybePrintBanner is gated behind DEBUG=pgserve (parity with G1 pg-seed pattern). Real warnings (retention failures, cwd-pin failures, profile instrumentation) keep emitting via // emit-discipline: ok — <reason> markers. New CI lint at tools/lint/emit-discipline-connection.ts (wired into bun run lint:emit) blocks future informational stderr from leaking back into connection/bootstrap modules.

  • Default invocation: genie ls --json produces clean JSON on stdout AND no [pgserve] connected to postgres line on stderr. — Live: ./dist/genie.js ls --json 2>&1 1>/dev/null | head -3 is silent.
  • DEBUG=pgserve genie ls --json 2>&1 1>/dev/null | head -5 retains today's verbose connection log. — Live: prints [pgserve] connected to postgres.
  • Real warnings/errors still emit at default verbosity. — All 4 pre-existing emits in db.ts (retention warning, GENIE_PROFILE_DB profile, two pgserve cwd WARNs) carry // emit-discipline: ok markers and remain default-on.
  • Lint rule fails on any new informational process.stderr.write in connection/bootstrap modules without the exemption comment. — Verified by removing the gate locally → bun run lint:emit reported 5 violations on db.ts. With the gate restored, lint is clean.
  • bun run check:fast includes the new lint rule. — scripts/lint-emit-discipline.ts imports and runs checkConnectionEmitDiscipline().

Validation

bun run check:fast                                      # PASS — typecheck + lint + dead-code + skills + wishes + emit-discipline
bun test src/term-commands/agents.test.ts -t "kill dedup paired"   # 5 pass / 0 fail
bun test src/lib/db.test.ts -t "no default stderr emit on connect"  # 6 pass / 0 fail

Live verification of G9:

./dist/genie.js ls --json 2>&1 1>/dev/null              # silent
DEBUG=pgserve ./dist/genie.js ls --json 2>&1 1>/dev/null # [pgserve] connected to postgres

G10 — Deferred

Post-update verify probe re-spec is blocked on a /trace pass into src/genie-commands/update.ts:362. The wish carries the deferred breadcrumb; do not attempt without trace results first.

Closes

#1677

Test plan

  • bun run check:fast — all gates green
  • G8 tests pass against live pgserve (5/5)
  • G9 tests pass (6/6)
  • Live dist/genie.js confirms default silent / DEBUG=pgserve verbose

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 7, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c0d0cf70-9e73-41d6-bc0a-6fb36369cf8e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cli-hygiene-round-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfe1516c29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/term-commands/agents.ts Outdated
`) as { id: string }[];
if (remaining.length > 0) return [];
const dirId = `dir:${displayName}`;
const removed = (await tx<{ id: string }[]>`DELETE FROM agents WHERE id = ${dirId} RETURNING id`) as {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict dir-shadow deletion to the same team

When killing a UUID row, this code deletes dir:<name> by id only after checking for remaining UUIDs in the killed row's team. If another team owns the dir:<name> row (while this team only has a UUID peer with the same custom_name), killing the UUID here will remove that other team's directory identity unexpectedly. This is reproducible with the cross-team sibling shape already used in tests (same custom_name in different teams) and can orphan the surviving team's runtime row.

Useful? React with 👍 / 👎.

Comment on lines +2843 to +2845
SELECT id FROM agents
WHERE custom_name = ${displayName} AND team IS NOT DISTINCT FROM ${team}
`) as { id: string }[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude the matched dir row from paired-id detection

The paired-row lookup for dir: kills selects by custom_name and team but does not exclude the matched row itself. Because directory rows normally have custom_name=<name>, paired will include w.id even when no UUID twin exists, causing false "paired row(s) also removed" output and spurious kill.dedup_paired audit events for single-row deletes.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request contains documentation and QA planning updates related to the 'CLI Noise and Hygiene Cleanup' wish, specifically documenting the audit results of recent fixes and the QA plan for verifying them. As there are no review comments provided for this pull request, I have no feedback to offer.

namastex888 and others added 5 commits May 7, 2026 13:26
…coped per reviewer

Lands the wish doc that scaffolds PR-A (#1634) and PR-B (#1636/#1637/#1638/
#1640/#1642), plus the 2026-05-07 PR-C draft + reviewer FIX-FIRST corrections.

Why this is a separate docs commit:
- The wish file was authored 2026-05-04 but only ever sat in a stash; never
  committed despite shipping work referencing it. This commit lands the
  reference document for completed + pending work in one place.
- PR-C as originally drafted had three invalid premises against live
  4.260507.1 (G3 amendment already implemented at scheduler-daemon.ts:1296;
  G9 line is on stderr not stdout; G10 design assumes binary-spawn that the
  HTTP probe doesn't do). Reviewer corrections folded in.
- Only G8 (kill-path shadow+UUID dedup) survives intact — file path
  corrected to src/term-commands/agents.ts:2817 (handleWorkerKill).
- G9 reframed as stderr-noise reduction (DEBUG=pgserve gating).
- G10 deferred pending /trace into update.ts:362.

QA dogfooding-72h artifacts (AUDIT.md, QA-PLAN.md) document the 72-h fix-audit
sweep that surfaced the bugs and triggered the wish update.

Refs: #1677

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
 #1677 (G8)

Killing a `dir:<name>` shadow OR its paired UUID twin now removes both halves
of the logical agent in one atomic transaction. Today's behavior left the
other half alive in `genie ls`, forcing operators into a second kill to clean
up the visual zombie (proven on 2026-05-07: 7× `dir:codex-*` kills left 7×
UUID twins in `error` state).

- New helper `killAgentWithDedup` in `src/term-commands/agents.ts` issues a
  single transactional cascade: `dir:` kill → all UUID twins for the same
  (name, team); UUID kill → `dir:` shadow when no other UUIDs share the name.
- New audit event `agent.kill.dedup_paired { matched, paired }` fires once
  per cascade for forensic traceability.
- `--keep-paired` escape hatch preserves today's single-row behavior for the
  rare case an operator wants the surviving half to study.
- Tests at `src/term-commands/agents.test.ts -t "kill dedup paired"` cover:
  kill-dir cascade, kill-UUID cascade, both `--keep-paired` variants, and
  cross-team isolation under migration 061's unique constraint.

Closes #1677 (G8 of wish cli-noise-and-hygiene-cleanup PR-C).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…closes #1677 (G9)

Every CLI invocation that touches the DB printed `[pgserve] connected to <db>`
on stderr. The line is on stderr (so JSON-on-stdout pipelines still work) but
clutters every operator terminal. Gate it behind `DEBUG=pgserve`, matching
the G1 pg-seed pattern; default-mode operator terminals stay quiet, debug
recovery still works.

- `src/lib/db.ts:maybePrintBanner` now requires `process.env.DEBUG?.includes('pgserve')`
  before emitting. Other audit-worthy stderr writes in the same file (retention
  warnings, pgserve cwd-pin failures, GENIE_PROFILE_DB instrumentation) get
  explicit `// emit-discipline: ok — <reason>` markers.
- New `_resetBannerForTest` export keeps the module-level `bannerPrinted` flag
  testable without touching production paths.
- `tools/lint/emit-discipline-connection.ts` adds a CI gate that flags any new
  informational `process.stderr.write` / `console.error` in
  connection/bootstrap modules without an exemption marker. Wired into
  `bun run check:fast` via `scripts/lint-emit-discipline.ts`.
- Tests at `src/lib/db.test.ts -t "no default stderr emit on connect"` pin the
  gating contract: default mode silent, `DEBUG=pgserve` (and comma-list
  variants) recover the line, plus a defense-in-depth source-string check
  that fails if the gate is ever removed.

Live verified on dist/genie.js:
  ./dist/genie.js ls --json 2>&1 1>/dev/null              # silent
  DEBUG=pgserve ./dist/genie.js ls --json 2>&1 1>/dev/null # one banner line

Closes #1677 (G9 of wish cli-noise-and-hygiene-cleanup PR-C).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…paired lookup

Two findings from Codex review on PR #1685:

- **P1 (high):** killing a UUID owned by team B was deleting `dir:<name>` even
  when the dir shadow belonged to team A. The dir-shadow delete now requires
  `team IS NOT DISTINCT FROM ${team}` so a UUID kill in one team can never
  orphan another team's directory identity. New regression test:
  `UUID kill respects team scope when dir shadow lives in another team`.
- **P2 (medium):** the dir-kill paired lookup matched the dir row itself when
  legacy shadows carry `custom_name = <name>` alongside the `dir:` prefix —
  emitting a false "paired row(s) also removed" message and a spurious
  `agent.kill.dedup_paired` audit event for what is really a single-row
  delete. The lookup now excludes the matched row and any other `dir:%` ids.
  New regression test:
  `dir kill with no UUID twins reports zero paired even when dir.custom_name is set`.

Also fixes the pgserve v2 smoke step in CI: G9 silenced the `[pgserve]
connected` banner by default, so the smoke needs to opt in via `DEBUG=pgserve`
to keep asserting the connection round-trip without re-introducing operator
stderr noise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@namastex888
namastex888 force-pushed the fix/cli-hygiene-round-2 branch from bfe1516 to 785ebd5 Compare May 7, 2026 16:30
@namastex888
namastex888 merged commit 740c018 into dev May 7, 2026
21 of 25 checks passed
@automagik-genie
automagik-genie deleted the fix/cli-hygiene-round-2 branch September 25, 2026 04:50
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