Add external event hooks for quota and provider state changes#2001
Add external event hooks for quota and provider state changes#2001jychp wants to merge 6 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 11:47 AM ET / 15:47 UTC. Summary Reproducibility: not applicable. as a bug reproduction: this is a new feature PR. The contributor supplied a terminal transcript for Review metrics: 2 noteworthy metrics.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Have a maintainer explicitly approve, narrow, or decline the guarded core hook surface before merging; once accepted, keep the current direct-exec, minimal-environment, redacted-payload design. Do we have a high-confidence way to reproduce the issue? Not applicable as a bug reproduction: this is a new feature PR. The contributor supplied a terminal transcript for Is this the best way to solve the issue? Unclear until maintainer direction is confirmed. If core hooks are accepted, the implementation is a reasonably narrow solution because it reuses AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c2d0cd1a1ff7. Label changesLabel changes:
Label justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (7 earlier review cycles)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1a5c65eaa
ℹ️ 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".
| if transition == .depleted { | ||
| self.emitQuotaReachedHook(provider: provider, sessionWindow: sessionWindow, snapshot: snapshot) |
There was a problem hiding this comment.
Emit quota hooks independently of notifications
This quota_reached hook is only reached after the sessionQuotaNotificationsEnabled guard above, so users who disable macOS session quota notifications but enable hooks will never receive external depletion events even on a real transition. Hooks have their own enable switch/config, so the hook emission should be tied to the quota transition rather than the notification preference; the same coupling affects quota_low through quotaWarningNotificationsEnabled.
Useful? React with 👍 / 👎.
Run user-configured external commands when quota/provider events occur, without CodexBar owning any switching logic. Hooks are opt-in and disabled by default. Core (CodexBarCore/Hooks): HookEvent (env vars + stdin JSON payload), HookRule/HooksConfig (matching, provider scoping, quota_low threshold gate, absolute-path requirement), HookRunner (delegates process work to the existing SubprocessRunner: shell-free exec, env injection, stdin, timeout, path validation, redacted logging), and an in-memory HookRateLimiter as a storm backstop. Adds an optional top-level `hooks` section to CodexBarConfig. App: emitHook dispatches fire-and-forget from the existing detection choke points so nothing blocks menu refresh: quota_low, quota_reached, quota_reset, provider_unavailable, provider_recovered, refresh_failed. Account labels respect hidePersonalInfo. Adds a Hooks preferences pane (enable toggle, trust warning, per-rule editing) wired through SettingsStore config mutators. CLI: `codexbar hooks list | enable | disable | test <event> --provider <p>`, sharing HookRunner with the app. Tests: unit coverage for matching, threshold gating, payload encoding, rate limiting, and the runner; localization catalogs updated for the new UI keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b1a5c65 to
2882485
Compare
…logs Address automated review feedback on steipete#2001: - Forward only a narrow allowlist of environment variables to hook commands (PATH/HOME/USER/SHELL/locale/TMPDIR) plus the event's CODEXBAR_* values, so a provider API key living in CodexBar's environment can never leak to a hook. - Rate-limit only the storm-prone events (provider_unavailable, refresh_failed). Quota events dedupe upstream; throttling them dropped a lower remaining-quota warning that crossed within the 10-minute window (Codex P2). - Redact hook failure logs: log the event and a coarse reason (exit code, timeout, not-found) instead of the subprocess stderr, which could echo the payload. Adds tests for the rate-limit classification and the environment allowlist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@clawsweeper re-review Pushed d7130ae addressing the review:
|
|
🦞🧹 I asked ClawSweeper to review this item again. |
Address the remaining P2 review points on steipete#2001: - Quota hooks no longer depend on notification preferences. Quota-warning and session-depletion detection now runs when either notifications OR a matching hook rule is enabled, and only the notification post is gated on the notification settings. Gating is keyed on hasQuotaHookRule(event:provider:), so behavior is byte-identical for users without a configured quota hook. - Editing hook rules no longer triggers a provider refresh. Hook config writes go through a dedicated path that persists and bumps a separate hooksRevision for the settings pane, without bumping configRevision (which drives the background refresh introduced by steipete#1996). Bundles the per-refresh quota-warning constants into QuotaWarningTransitionContext to keep the transition helper within the parameter-count limit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@clawsweeper re-review Pushed fa11d2d addressing the remaining P2 items:
Quota/notification transition suites and hooks tests are green. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Address ClawSweeper's remaining P2 blocker (UsageStore+QuotaWarnings.swift): quota_low hooks were emitted only on notification-threshold crossings, so a rule at threshold 0.90 could never fire under the default 50/20 remaining notification thresholds. quota_low hooks now run on a dedicated path, fully separate from the notification machinery: - Per-lane usage is tracked and each matching rule fires when usage crosses its own threshold upward. A rule without a threshold falls back to the provider's notification thresholds so a plain low-quota hook still fires at the app's warning points. - The notification path reverts to its original notification-only form; hooks no longer piggyback on it. - The crossing selection is extracted to QuotaLowHookThreshold.crossedRules in Core and unit-tested (own-threshold crossing, thresholdless fallback, and selecting only the newly-crossed rule among several). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@clawsweeper re-review Pushed 247bcb7 fixing the remaining P2 (quota_low threshold contract):
All hooks + quota/notification transition suites green; make lint clean. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
…t-hooks # Conflicts: # Sources/CodexBar/SettingsStore+ConfigPersistence.swift # Sources/CodexBar/UsageStore.swift
Address ClawSweeper's remaining P2 finding (UsageStore+Refresh.swift): the refresh_failed hook forwarded error.localizedDescription into CODEXBAR_STATUS and the JSON payload, and provider errors can embed response-body previews. The hook now receives a coarse, non-secret category (timeout, offline, network_error, auth_required, cancelled, error) via refreshFailureHookStatus, derived from the error type and NSURLError code only. Adds a test asserting URL errors map to categories and that a body-bearing provider error never leaks its description. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@clawsweeper re-review Pushed 24d4e96 addressing the remaining P2 (refresh_failed payload redaction):
Also merged current main (resolved the background-work config conflict by adopting updateConfig(affectsBackgroundWork:) and dropping the parallel hooksRevision). make lint clean; hooks + quota/notification/localization suites green. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Closes #2000.
What
Optional, opt-in hooks that run a user-configured command when a quota or provider event fires. CodexBar detects the event and runs the command; it does not switch providers, restart apps, or store keys.
Events:
quota_low(with a per-rule usage threshold),quota_reached,quota_reset,provider_unavailable,provider_recovered,refresh_failed.How
config.jsonunder a top-levelhooksblock, so the app and CLI share it. Off by default.SubprocessRunner: direct exec (no shell), timeout with SIGTERM then SIGKILL, absolute-path validation. Dispatch is fire-and-forget so it never blocks the menu refresh.codexbar hooks list | enable | disable | test.Security boundary
CODEXBAR_*values. CodexBar's own environment is not inherited, so a provider key living in env cannot reach a hook.provider_unavailable,refresh_failed) are rate limited. Quota events dedupe upstream, so they are not throttled.Payload
Env vars (
CODEXBAR_EVENT,CODEXBAR_PROVIDER,CODEXBAR_USAGE_PERCENT,CODEXBAR_RESET_AT, ...) plus a JSON payload on stdin.Proof
Real CLI run against an isolated config (
CODEXBAR_CONFIG), hook script pointed at a logger:The env-allowlist test also confirms a secret in the base environment (
ANTHROPIC_API_KEY) does not reach the child, whilePATHdoes.Tests
Unit coverage for rule matching, threshold gating, payload encoding, rate-limit classification, the environment allowlist, and the runner (exec + missing executable). Localization catalogs updated for the new UI strings.
swift build,make lint, and the hooks and localization suites are green.Notes
mainso PR Fix visual settings refresh #1996 (visual settings refresh) is included; nothing from it is reverted.updateConfig(affectsBackgroundWork: false), so the config persists and the pane re-renders without scheduling a background refresh (reuses main's refresh-isolation mechanism).quota_lowhooks are driven by each rule's own usage threshold (crossed upward), independently of the notification thresholds and preferences. A rule without a threshold falls back to the provider's notification thresholds. Crossing selection lives inQuotaLowHookThreshold.crossedRules(Core) and is unit-tested.Non-goals
No provider switching, no editing other tools' config, no app restarts, no storing keys, no vendor fallback chains, no running commands from remote config.
🤖 Generated with Claude Code