Skip to content

Sound / completion notifier: terminal bell + OSC-9 on turn completion & awaiting-input - #149

Merged
gnanam1990 merged 7 commits into
mainfrom
sound-notify
Jun 9, 2026
Merged

Sound / completion notifier: terminal bell + OSC-9 on turn completion & awaiting-input#149
gnanam1990 merged 7 commits into
mainfrom
sound-notify

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a dep-free completion / awaiting-input notifier: a terminal bell and/or OSC-9 desktop notification when Zero finishes a turn or needs your input. Opt-in and byte-identical when unconfigured (default off). No new module dependencies.

  • Mechanism (notify.mode): off (default) | bell (BEL) | notify (OSC-9 desktop notification ESC ]9; <msg> BEL) | both.
  • Triggers: run completion (ready for input) and awaiting-input (a permission prompt or ask_user question).
  • Focus gating (notify.focusMode, TUI only): unfocused (default — only when the terminal is NOT focused) | always | focused. Uses Bubble Tea focus reporting.
  • Surface: config keys + zero exec --notify <mode> / --no-notify. A TUI /notify picker is a deliberate follow-up.

Design

  • internal/notify (new, pure, stdlib-only): Mode/FocusMode/Event, Notifier{New, SetFocused, Notify}, DefaultMessage, Enabled. Two pure helpers (shouldEmit, sequence) carry the policy and are fully unit-tested without a terminal. Messages are generic ("Zero: ready" / "Zero: needs input") — no prompt/response content (privacy) — and sanitized (ESC/BEL/control stripped, clamped to 120 runes) so they can't break the escape.
  • Config (internal/config): notify.mode / notify.focusMode, validated at Resolve (invalid → error), mirroring the SandboxConfig pattern (incl. Overrides/merge threading, for parity with the existing forward-plumbing pattern).
  • TUI (internal/tui): focus reporting enabled only when notify is on (so off stays byte-identical); FocusMsg/BlurMsg track focus; completion fires once per finished foreground run (excluded from cancel/quit/background-drain paths); permission/ask_user wrappers fire awaiting-input. Notifier writes to os.Stderr (out-of-band; never the stdout render target).
  • exec (internal/cli): notifier on stderr (never stdout — may carry stream-json); fires completion after agent.Run on both the normal and --use-spec paths, on success and error. Headless has no focus signal, so exec ignores focusMode and always emits when a mode is set.

Test Plan

  • go build ./... / GOOS=windows GOARCH=amd64 go build ./... clean
  • go vet ./... clean; gofmt clean (touched files)
  • go test ./... green (new tests across notify, config, cli, tui)
  • go test -race ./internal/{notify,config,cli,tui}/... green
  • Adversarial whole-diff review: found + fixed a real gap (exec --use-spec never notified) and hardened headless focus handling + off-state byte-identity

Reviewer focus

  • Off = byte-identical: with mode unset/off, no notifier output and focus reporting is not enabled.
  • No stdout corruption: both TUI and exec notifiers target stderr; stdout (stream-json) is never the sink.
  • Completion fires once on the foreground run, on success and error, never on cancel/drain.

Known follow-ups (not blockers)

  • An invalid --notify <value> is silently no-op (config values are validated and error). A small notify.Valid(mode) guard for the flag is a clean follow-up.
  • TUI /notify interactive picker.

Summary by CodeRabbit

  • New Features

    • CLI flags to control per-run notifications (--notify / --no-notify) and updated help text
    • Notifications for task completion and when awaiting user input; TUI integrates and respects focus state
    • Configurable modes: bell, desktop notification, or both
  • Config

    • Persistent notification settings with validation and merge/override support
  • Tests

    • Unit tests for flag parsing, mode resolution, config validation, notifier behavior, and TUI focus handling

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: e62c6d51d699
Changed files (14): internal/cli/app.go, internal/cli/exec.go, internal/cli/exec_parse.go, internal/cli/exec_spec.go, internal/cli/exec_test.go, internal/config/resolver.go, internal/config/resolver_test.go, internal/config/types.go, internal/notify/notify.go, internal/notify/notify_test.go, internal/tui/model.go, internal/tui/model_test.go, and 2 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b302a5d4-3413-45a4-91fc-7195e0f8f27f

📥 Commits

Reviewing files that changed from the base of the PR and between 4019303 and e62c6d5.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/cli/app.go
  • internal/config/resolver.go

Walkthrough

Adds a dep-free terminal notification package (BEL/OSC-9) with focus gating, wires notify settings through config/resolver/types, exposes per-run CLI flags --notify/--no-notify with validation and precedence, and integrates notifications into exec and TUI flows (awaiting-input and completion events), plus tests.

Changes

Notification system implementation and integration

Layer / File(s) Summary
Core notification package and tests
internal/notify/notify.go, internal/notify/notify_test.go
New concurrent-safe Notifier emitting BEL and OSC-9, message sanitization, focus gating, DefaultMessage/Enabled helpers, and comprehensive tests covering policy, output format, sanitization, and concurrency.
Config types and resolver for notifications
internal/config/types.go, internal/config/resolver.go, internal/config/resolver_test.go
Adds NotifyConfig and wires it through FileConfig/Overrides/ResolvedConfig; resolver validates notify.mode and notify.focusMode, and merge/apply logic propagates non-empty notify fields. Tests cover valid/invalid values and empty passthrough.
Exec CLI flags and parsing tests
internal/cli/exec.go, internal/cli/exec_parse.go, internal/cli/exec_test.go
Parses --notify <mode> (also --notify=<mode>) and --no-notify; validates exclusivity and allowed values (off,bell,notify,both); execNotifyMode resolves effective mode with precedence; tests validate parsing and resolution.
Exec notifier wiring and completion emission
internal/cli/exec.go, internal/cli/exec_spec.go
Constructs a notifier for runExec from resolved/override mode and headless focus rule, threads it into execSpecDraftRun, and emits Completion via the notifier after a successful run.
TUI notifier initialization and event handling
internal/tui/options.go, internal/tui/model.go, internal/tui/run.go, internal/tui/model_test.go
Adds Notify to tui.Options; model initializes a notifier from options, toggles focus on Bubble Tea focus/blur messages, emits AwaitingInput for permission/ask_user (ask_user only when questions exist), and emits Completion on successful responses. Focus reporting enabled when notifications active; test validates focus-dependent notifications.
App TUI launch wiring and help text
internal/cli/app.go
Passes resolved.Notify into tui.Options when launching the TUI and updates zero exec help text to document --notify/--no-notify and valid modes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • Vasanthdev2004
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a terminal bell and OSC-9 desktop notification feature for completion and awaiting-input events.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sound-notify

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
internal/cli/exec_test.go (1)

1776-1813: ⚡ Quick win

Expand notify tests to cover invalid values and stderr-only emission.

These tests cover precedence and conflict, but they don’t guard the two brittle paths: rejecting invalid --notify values and ensuring notification bytes never leak into stdout (especially stream-json mode).

✅ Suggested additional cases
+func TestParseExecNotifyRejectsInvalidValue(t *testing.T) {
+	_, _, err := parseExecArgs([]string{"--notify", "loud", "hello"})
+	if err == nil || !strings.Contains(err.Error(), "invalid --notify") {
+		t.Fatalf("expected invalid --notify error, got %v", err)
+	}
+}
+
+func TestRunExecNotifyBellWritesOnlyToStderr(t *testing.T) {
+	exitCode, stdout, stderr := runExecWithEcho(t, []string{"exec", "--notify", "bell", "--output-format", "stream-json", "hello"})
+	if exitCode != exitSuccess {
+		t.Fatalf("exitCode = %d stdout=%s stderr=%s", exitCode, stdout, stderr)
+	}
+	if strings.Contains(stdout, "\a") {
+		t.Fatalf("stdout must not contain BEL, got %q", stdout)
+	}
+	if !strings.Contains(stderr, "\a") {
+		t.Fatalf("expected BEL on stderr, got %q", stderr)
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/exec_test.go` around lines 1776 - 1813, Add two tests to
exec_test.go: one that calls parseExecArgs with an invalid --notify value (e.g.,
"--notify", "invalid", "cmd") and asserts an error is returned to exercise
validation in parseExecArgs, and another that runs the exec path in stream-json
mode (using execOptions/execCommand helper or invoking the same code path used
by execOptions) to assert any notification output is written to stderr and
nothing is written to stdout (capture both streams and check stderr contains
notification bytes while stdout is empty) so notification bytes never leak into
stdout; reference parseExecArgs and execNotifyMode to locate parsing/resolve
logic and the exec code path that emits notifications.
internal/tui/model_test.go (1)

1300-1327: ⚡ Quick win

Add notifier regression coverage for zero-question ask_user.

This test covers focus-gated completion well. Please also add a case proving no AwaitingInput emit when ask_user has zero questions, so the no-input fast-path stays quiet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model_test.go` around lines 1300 - 1327, Add a test case that
calls the model's ask_user helper with an empty question list and asserts the
notifier does not emit an AwaitingInput event: using the existing test's model
instance (model, m.notifier) after any desired focus state, call ask_user with
zero questions (e.g., ask_user(nil) or ask_user([]Question{})) and verify the
notify buffer stays empty (buf.Len()==0) and that no notify.AwaitingInput string
was written; reference the ask_user helper, the model type, and
notify.AwaitingInput to locate and implement the assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/exec_parse.go`:
- Around line 31-44: The notify flag parsing currently assigns any string to
options.notifyMode in both the separate-flag branch (using nextFlagValue) and
the inline branch (using requiredInlineFlagValue); add validation after
obtaining value to accept only "off", "bell", "notify", or "both" and return an
error for any other value so invalid CLI overrides are rejected early; update
the branches that set options.notifyMode (the case handling arg == "--notify"
and the case handling strings.HasPrefix(arg, "--notify=")) to validate the
trimmed value and return the parse error, and make the same change at the other
parsing location that sets options.notifyMode (the code around the other
occurrence noted).

In `@internal/config/resolver_test.go`:
- Around line 1289-1290: Tests calling Resolve are flaky because they rely on
ambient process environment; update each failing test to provide a deterministic
Env by setting ResolveOptions.Env to an explicit empty map (or a controlled map
of expected keys) instead of leaving it nil so Resolve does not call os.Getenv.
Locate calls like Resolve(ResolveOptions{UserConfigPath: path}) in
resolver_test.go and change them to include Env: map[string]string{} (or the
specific env entries needed) for each occurrence (including the other test calls
noted) so the resolver behavior is deterministic.

In `@internal/tui/model.go`:
- Around line 1134-1136: The AwaitingInput notification is sent unconditionally
via m.notifier.Notify(notify.AwaitingInput,
notify.DefaultMessage(notify.AwaitingInput)) even when the ask_user path
auto-resolves empty questions; change the code so you only call
m.notifier.Notify(...) when input is actually required (e.g., check the
questions slice or the ask_user decision that would block — ensure the check
covers the empty-questions auto-resolve path), by moving the notification after
that check (or gating it with a condition like len(questions)>0 or
!autoResolved) so spurious AwaitingInput notifications are not emitted.

---

Nitpick comments:
In `@internal/cli/exec_test.go`:
- Around line 1776-1813: Add two tests to exec_test.go: one that calls
parseExecArgs with an invalid --notify value (e.g., "--notify", "invalid",
"cmd") and asserts an error is returned to exercise validation in parseExecArgs,
and another that runs the exec path in stream-json mode (using
execOptions/execCommand helper or invoking the same code path used by
execOptions) to assert any notification output is written to stderr and nothing
is written to stdout (capture both streams and check stderr contains
notification bytes while stdout is empty) so notification bytes never leak into
stdout; reference parseExecArgs and execNotifyMode to locate parsing/resolve
logic and the exec code path that emits notifications.

In `@internal/tui/model_test.go`:
- Around line 1300-1327: Add a test case that calls the model's ask_user helper
with an empty question list and asserts the notifier does not emit an
AwaitingInput event: using the existing test's model instance (model,
m.notifier) after any desired focus state, call ask_user with zero questions
(e.g., ask_user(nil) or ask_user([]Question{})) and verify the notify buffer
stays empty (buf.Len()==0) and that no notify.AwaitingInput string was written;
reference the ask_user helper, the model type, and notify.AwaitingInput to
locate and implement the assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 89ae58ff-acf5-4a44-9afc-7101e4b70158

📥 Commits

Reviewing files that changed from the base of the PR and between b83367e and 74a0c61.

📒 Files selected for processing (14)
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/cli/exec_parse.go
  • internal/cli/exec_spec.go
  • internal/cli/exec_test.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/notify/notify.go
  • internal/notify/notify_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/options.go
  • internal/tui/run.go

Comment thread internal/cli/exec_parse.go
Comment thread internal/config/resolver_test.go Outdated
Comment thread internal/tui/model.go Outdated

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: Request changes

Findings:

  • P2 internal/cli/exec_parse.go:31: --notify accepts any string. Config validation rejects invalid modes, but CLI overrides like --notify buz silently run with no notification. Please validate the CLI override during parse and accept only off, bell, notify, or both.

  • P2 internal/tui/model.go:1134: AwaitingInput notification fires before checking whether ask_user actually has questions. Empty question prompts auto-resolve, so this can send a false bell/desktop notification. Gate the notification on len(request.Questions) > 0 or move it after the non-empty check.

  • P3 internal/config/resolver_test.go:1289: notify resolver tests should pass Env: map[string]string{}. The resolver falls back to os.Getenv when Env is nil, so these tests can become ambient-env dependent.

Validation I ran locally on the PR worktree:

  • git diff --check origin/main...HEAD
  • go test ./internal/notify ./internal/cli ./internal/tui ./internal/config
  • go test ./...

All validation passed, but the issues above should be fixed before merge.

…ministic resolver tests

Addresses #149 review (CodeRabbit + @Vasanthdev2004):
- parseExecArgs rejects an invalid --notify value (only off/bell/notify/both),
  so a CLI typo errors early instead of silently running with no notification.
- TUI OnAskUser fires AwaitingInput only when len(request.Questions) > 0 — a
  request with no questions auto-resolves without prompting, so the bell/desktop
  notification was a false positive.
- notify resolver tests pass Env: map[string]string{} so Resolve does not fall
  back to os.Getenv (deterministic, not ambient-env dependent).
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@Vasanthdev2004 all three addressed in 4019303:

  • P2 --notify is validated at parse (off/bell/notify/both only) + test added.
  • P2 AwaitingInput now gated on len(request.Questions) > 0 so empty/auto-resolving ask_user prompts no longer send a false bell.
  • P3 notify resolver tests pass Env: map[string]string{}.

go test ./... + -race on touched packages + GOOS=windows build all green locally.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

@gnanam1990 I’ll review the updated changes now, with focus on the addressed CLI validation, AwaitingInput gating, resolver test environment setup, and the notifier behavior called out in the PR objectives.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

# Conflicts:
#	internal/config/resolver.go
#	internal/config/types.go

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/exec.go`:
- Around line 246-249: The current logic only disables deferral when tool_search
is explicitly in options.disabledTools, but misses the case where an allow-list
is active and omits tool_search; update the guard around effectiveDeferThreshold
so it also treats tool_search as "disabled" when options.enabledTools is
non-empty and does not contain tools.ToolSearchToolName. Concretely, modify the
condition that sets effectiveDeferThreshold = 0 (near
registerToolSearchIfEligible/effectiveDeferThreshold) to check: if
toolListContains(options.disabledTools, tools.ToolSearchToolName) OR
(len(options.enabledTools) > 0 && !toolListContains(options.enabledTools,
tools.ToolSearchToolName)) then set effectiveDeferThreshold = 0 before calling
registerToolSearchIfEligible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b302a5d4-3413-45a4-91fc-7195e0f8f27f

📥 Commits

Reviewing files that changed from the base of the PR and between 4019303 and e62c6d5.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/cli/app.go
  • internal/config/resolver.go

@coderabbitai coderabbitai 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/exec.go`:
- Around line 246-249: The current logic only disables deferral when tool_search
is explicitly in options.disabledTools, but misses the case where an allow-list
is active and omits tool_search; update the guard around effectiveDeferThreshold
so it also treats tool_search as "disabled" when options.enabledTools is
non-empty and does not contain tools.ToolSearchToolName. Concretely, modify the
condition that sets effectiveDeferThreshold = 0 (near
registerToolSearchIfEligible/effectiveDeferThreshold) to check: if
toolListContains(options.disabledTools, tools.ToolSearchToolName) OR
(len(options.enabledTools) > 0 && !toolListContains(options.enabledTools,
tools.ToolSearchToolName)) then set effectiveDeferThreshold = 0 before calling
registerToolSearchIfEligible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b302a5d4-3413-45a4-91fc-7195e0f8f27f

📥 Commits

Reviewing files that changed from the base of the PR and between 4019303 and e62c6d5.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go
  • internal/config/types.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/config/resolver_test.go
  • internal/config/types.go
  • internal/cli/app.go
  • internal/config/resolver.go
🛑 Comments failed to post (1)
internal/cli/exec.go (1)

246-249: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle enabledTools allow-list dead-end for tool_search.

Deferral is disabled when tool_search is in disabledTools, but not when an allow-list is active and omits tool_search. That can still activate deferral and hide deferred tools behind an unreachable loader.

Suggested fix
 effectiveDeferThreshold := resolved.Tools.DeferThreshold
 if toolListContains(options.disabledTools, tools.ToolSearchToolName) {
 	effectiveDeferThreshold = 0
 }
+if len(options.enabledTools) > 0 && !toolListContains(options.enabledTools, tools.ToolSearchToolName) {
+	effectiveDeferThreshold = 0
+}
 registerToolSearchIfEligible(registry, effectiveDeferThreshold, permissionMode, options.enabledTools, options.disabledTools)

Also applies to: 567-575

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/exec.go` around lines 246 - 249, The current logic only disables
deferral when tool_search is explicitly in options.disabledTools, but misses the
case where an allow-list is active and omits tool_search; update the guard
around effectiveDeferThreshold so it also treats tool_search as "disabled" when
options.enabledTools is non-empty and does not contain tools.ToolSearchToolName.
Concretely, modify the condition that sets effectiveDeferThreshold = 0 (near
registerToolSearchIfEligible/effectiveDeferThreshold) to check: if
toolListContains(options.disabledTools, tools.ToolSearchToolName) OR
(len(options.enabledTools) > 0 && !toolListContains(options.enabledTools,
tools.ToolSearchToolName)) then set effectiveDeferThreshold = 0 before calling
registerToolSearchIfEligible.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Re: the exec.go effectiveDeferThreshold guard (your 08:26 review) — this is intentionally not changing, and the suggestion would break tested behavior.

tool_search is allowlist-exempt by design. An --enabled-tools allowlist that omits tool_search must still keep it reachable so the model can load the allowlisted deferred tools:

  • internal/agent/loop.go:407-412 (executeToolCall): the allowlist is exempted for tool_search; only an explicit --disabled-tools tool_search rejects it.
  • internal/agent/loop.go:935-936 (partitionTools): the EnabledTools allowlist is intentionally NOT checked for the loader.
  • TestAllowlistedDeferredToolsKeepToolSearchReachable asserts that with the allowlist omitting tool_search, deferral activates, tool_search stays exposed, and a tool_search call is dispatch-allowed.

So zeroing effectiveDeferThreshold when the allowlist omits tool_search would disable deferral for the exact scenario it's designed to support, and the exec registration gate would then disagree with the loop's partition gate. The guard correctly keys only on disabledTools. (This code is from #146/#148 already on main; it appears here only via the merge.) Could you resolve this one?

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List CodeRabbit review comments on PR `#149` to find the effectiveDeferThreshold comment
gh api repos/Gitlawb/zero/pulls/149/comments --paginate \
  --jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, body: (.body | .[0:200]), url: .html_url}' 2>/dev/null || \
gh api repos/Gitlawb/zero/pulls/149/comments --paginate \
  --jq '.[] | select(.user.login | startswith("coderabbit")) | {id: .id, body: (.body | .[0:200]), url: .html_url}'

Length of output: 2345


🏁 Script executed:

#!/bin/bash
# Check PR reviews for the effectiveDeferThreshold comment
gh api repos/Gitlawb/zero/pulls/149/reviews --paginate \
  --jq '.[] | select(.user.login == "coderabbitai[bot]") | {id: .id, body: (.body | .[0:500]), state: .state}'

# Also search all review comments (including from reviews) for effectiveDeferThreshold
gh api repos/Gitlawb/zero/pulls/149/comments --paginate \
  --jq '.[] | select(.body | test("effectiveDeferThreshold"; "i")) | {id: .id, body: (.body | .[0:300]), url: .html_url}'

Length of output: 2620



@gnanam1990 — your explanation is correct and well-documented. The effectiveDeferThreshold guard keying only on disabledTools (not enabledTools) is the right behavior: tool_search is allowlist-exempt by design so the model can always reach it to load deferred tools, and TestAllowlistedDeferredToolsKeepToolSearchReachable locks that contract in place. My suggestion would have broken exactly the scenario the guard is designed to support.

One thing to note: it looks like that inline comment never successfully posted — GitHub returned an internal server error when the review tried to submit it (review 4456701295 has the caution banner "Inline review comments failed to post"). There's no resolvable thread in the GitHub UI for it. You shouldn't see an open thread for it on the PR, but if you do, let me know and I'll dig further.


✏️ Learnings added
Learnt from: gnanam1990
URL: https://github.com/Gitlawb/zero/pull/149

Timestamp: 2026-06-09T08:34:56.167Z
Learning: In the repository Gitlawb/zero, `tool_search` is allowlist-exempt by design. When an `--enabled-tools` allowlist is active and omits `tool_search`, deferral should still activate and `tool_search` must remain reachable so the model can load allowlisted deferred tools. The `effectiveDeferThreshold` guard in `internal/cli/exec.go` intentionally keys only on `disabledTools` (not `enabledTools`), consistent with `internal/agent/loop.go:407-412` (`executeToolCall`) and `internal/agent/loop.go:935-936` (`partitionTools`), and is validated by `TestAllowlistedDeferredToolsKeepToolSearchReachable`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: approve.

Rereviewed latest head e62c6d5.

The earlier blockers are resolved:

  • --notify now validates values and rejects invalid modes.
  • ask_user with zero questions no longer emits AwaitingInput before the auto-resolve path.
  • notify config tests use explicit empty env maps.

I also rechecked the latest CodeRabbit enabledTools/tool_search concern. That does not apply after the merged deferred-tool behavior: tool_search is deliberately exposed and dispatch-allowed on the active path even when an allowlist omits it, while explicit deny still wins.

Validation run locally on the PR worktree:

  • git diff --check 2ea36cf138e559af947b58e360d8f0d8fc8bdf80...HEAD
  • go test ./internal/notify ./internal/cli ./internal/config ./internal/tui
  • go test ./...

All passed.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review — the lone finding from the 08:26 run was confirmed a non-issue (tool_search is allowlist-exempt by design; you agreed above), and per your note its inline comment never actually posted. No code changes outstanding. Please re-review so the stale CHANGES_REQUESTED clears.

@Vasanthdev2004 — all three of your findings were addressed in commit 4019303: (1) --notify value validated at parse, (2) AwaitingInput gated on len(request.Questions) > 0, (3) notify resolver tests pass explicit Env. Plus the branch is now merged up to main (deferred-tools conflict resolved). Ready for re-review when you have a moment.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

@gnanam1990 — acknowledged. The effectiveDeferThreshold concern is settled (non-issue, already learned), and no open threads remain from my side. Kicking off a fresh review now to clear the stale state.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit approve

@gnanam1990
gnanam1990 merged commit 58e209d into main Jun 9, 2026
6 checks passed
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

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.

2 participants