Skip to content

Add external event hooks for quota and provider state changes#2001

Merged
steipete merged 19 commits into
steipete:mainfrom
jychp:feature/external-event-hooks
Jul 17, 2026
Merged

Add external event hooks for quota and provider state changes#2001
steipete merged 19 commits into
steipete:mainfrom
jychp:feature/external-event-hooks

Conversation

@jychp

@jychp jychp commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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 lives in the shared config.json under a top-level hooks block, so the app and CLI share it. Off by default.
  • Detection reuses the existing transition points (quota warnings, session depletion, reset detection, status changes, fetch failures), so this is mostly wiring, not a new engine.
  • Execution reuses 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.
  • Surfaces: a Hooks settings pane and codexbar hooks list | enable | disable | test.

Security boundary

  • The hook process gets a minimal environment: an allowlist of general vars (PATH, HOME, USER, SHELL, locale, TMPDIR) plus the event's CODEXBAR_* values. CodexBar's own environment is not inherited, so a provider key living in env cannot reach a hook.
  • No secrets in the payload; the account field is omitted when "hide personal info" is on.
  • Failure logs are redacted: only the event and a coarse reason (exit code, timeout, not found), never the command's stderr.
  • Only the events that can repeat every refresh (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:

$ codexbar hooks list
Hooks: enabled
[on] quota_reached provider=codex: /path/to/hook.sh switch codex openai

$ codexbar hooks test quota_reached --provider codex
ran /path/to/hook.sh: OK — ok

# recorded by the hook (environment + stdin JSON):
env: quota_reached codex usage=0.95 window=session
stdin: {"event":"quota_reached","provider":"codex","timestamp":"2026-07-09T04:42:17Z","usagePercent":0.95,"window":"session"}

The env-allowlist test also confirms a secret in the base environment (ANTHROPIC_API_KEY) does not reach the child, while PATH does.

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

  • Rebased onto current main so PR Fix visual settings refresh #1996 (visual settings refresh) is included; nothing from it is reverted.
  • Quota hooks are decoupled from notification preferences: detection runs when either notifications or a matching hook rule is enabled, and only the notification post is gated on the notification settings. Users without a configured quota hook see byte-identical behavior.
  • Editing hook rules does not trigger a provider refresh: hook writes go through updateConfig(affectsBackgroundWork: false), so the config persists and the pane re-renders without scheduling a background refresh (reuses main's refresh-isolation mechanism).
  • quota_low hooks 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 in QuotaLowHookThreshold.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

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed July 17, 2026, 1:36 AM ET / 05:36 UTC.

Summary
Adds opt-in external command hooks for six quota and provider events across shared configuration, settings UI, CLI commands, runtime dispatch, documentation, localization, and tests.

Reproducibility: yes. for the patch defects: the current-head source establishes deterministic paths for a synthetic quota sample, a repeated outage after recovery, and selected-account refresh failures. Live account access is unnecessary to demonstrate these control-flow errors.

Review metrics: 3 noteworthy metrics.

  • Feature surface: 60 files; 2,644 added, 136 removed. The patch spans runtime behavior, persistent config, CLI, settings UI, documentation, localization, and tests.
  • Event contract: 6 event types. Each event becomes a user-visible automation contract whose transition semantics must remain stable.
  • Blocking defects: 3 P2 findings. Each defect can suppress or falsely emit a configured hook on a real state path.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #2000
Summary: This PR is the direct implementation candidate for the open external-hooks request; the merged account-scoping work is supporting infrastructure rather than a competing fix.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🐚 platinum hermit
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Fix and regression-test the three remaining event-delivery paths.
  • [P2] Add focused fresh-config and existing-config upgrade validation for the shared hooks block.
  • Obtain explicit product-owner acceptance of the external-process hook contract.

Risk before merge

  • [P1] The shared hooks block and six event names become a persistent automation contract that user scripts may depend on across future upgrades.
  • [P1] Merging introduces an intentional local-code-execution boundary; maintainers must explicitly accept ownership of its configuration, environment, payload, and logging contract.

Maintainer options:

  1. Repair delivery, then approve (recommended)
    Fix the three bounded transition defects and require fresh-install, upgrade, and redacted execution proof before merge.
  2. Reduce the lifecycle contract
    Remove provider and refresh-failure events and land only quota hooks if maintainers want a smaller initial compatibility surface.
  3. Pause external execution
    Close or pause the PR if CodexBar should not own a persistent local process-execution feature.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Fix the three current P2 event-delivery findings without expanding the hook API; add focused regressions for synthetic quota windows, outage-to-recovery-to-outage transitions, and selected-account refresh failures, while preserving the environment allowlist and redacted logging.

Next step before merge

  • [P2] Three bounded control-flow defects have clear source areas and deterministic regression seams; product acceptance remains a separate decision after repair.

Maintainer decision needed

  • Question: Should CodexBar permanently expose user-configured external process hooks as a shared app and CLI configuration surface after the delivery defects are repaired?
  • Rationale: Correctness repairs cannot determine whether a persistent local-command execution and six-event automation contract belongs within CodexBar’s product boundary.
  • Likely owner: steipete — They authored the latest branch hardening and integration work and are the strongest current-history owner for this product boundary.
  • Options:
    • Accept guarded hooks (recommended): Land the opt-in direct-exec design after the three delivery defects and upgrade proof are complete.
    • Narrow the event set: Approve only quota events and defer provider and failure events until their lifecycle semantics are more stable.
    • Decline core hooks: Keep CodexBar observational and do not add a user-configured external process surface.

Security
Cleared: The patch uses opt-in absolute-path direct execution, a narrow inherited-environment allowlist, bounded payload and timeout limits, and redacted failure logging; no concrete remaining secret-leak or supply-chain defect is established.

Review findings

  • [P2] Skip synthetic quota windows before storing hook baselines — Sources/CodexBar/UsageStore+QuotaWarnings.swift:114
  • [P2] Allow a new outage after an intervening recovery — Sources/CodexBarCore/Hooks/HookEvent.swift:21
  • [P2] Emit refresh failures from selected-account result paths — Sources/CodexBar/UsageStore+Refresh.swift:1090
Review details

Best possible solution:

Fix the three event-delivery gaps without expanding the API, preserve the opt-in direct-exec safeguards, validate both fresh and existing shared configurations, and then obtain explicit maintainer acceptance of the automation contract.

Do we have a high-confidence way to reproduce the issue?

Yes for the patch defects: the current-head source establishes deterministic paths for a synthetic quota sample, a repeated outage after recovery, and selected-account refresh failures. Live account access is unnecessary to demonstrate these control-flow errors.

Is this the best way to solve the issue?

No, not yet: the generic direct-exec hook architecture is plausible, but three event paths violate its promised transition contract and the permanent product boundary remains unapproved.

Full review comments:

  • [P2] Skip synthetic quota windows before storing hook baselines — Sources/CodexBar/UsageStore+QuotaWarnings.swift:114
    The quota-low hook path records Claude’s synthetic no-live-session placeholder as a real 0% baseline. When the first real sample is already above a configured threshold, this creates a false crossing and runs the hook even though no real quota lane crossed; mirror the notification path’s synthetic-window guard before updating hook history.
    Confidence: 0.96
  • [P2] Allow a new outage after an intervening recovery — Sources/CodexBarCore/Hooks/HookEvent.swift:21
    provider_unavailable is already emitted only on a healthy-to-outage transition, but the ten-minute limiter keys only on event, provider, account, and window. An outage-to-recovery-to-outage sequence inside that window drops the second real outage; remove this transition event from time-window limiting or clear its limiter state on recovery.
    Confidence: 0.98
  • [P2] Emit refresh failures from selected-account result paths — Sources/CodexBar/UsageStore+Refresh.swift:1090
    The new refresh_failed call is only in the unscoped provider failure branch. Selected token-account and selected Codex visible-account outcomes update their errors in separate result paths without emitting the hook, so configured-account users miss the event; route those failures through the same redacted emission helper.
    Confidence: 0.95

Overall correctness: patch is incorrect
Overall confidence: 0.94

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b7920f1aead5.

Label changes

Label justifications:

  • P2: This opt-in feature has three concrete event-delivery defects and a broad new automation contract, warranting normal-priority repair and review.
  • merge-risk: 🚨 compatibility: The PR adds persistent shared configuration and six event names that user scripts may rely on across upgrades.
  • merge-risk: 🚨 security-boundary: The PR executes user-configured local binaries and forwards event metadata, making its environment, payload, and logging boundaries merge-critical.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes an isolated after-fix CLI run showing a configured rule executing and the child receiving expected event environment values plus JSON stdin.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes an isolated after-fix CLI run showing a configured rule executing and the child receiving expected event environment values plus JSON stdin.
Evidence reviewed

Acceptance criteria:

  • [P1] swift test --filter HooksTests.
  • [P1] swift test --filter HookDispatchTests.
  • [P1] swift test --filter QuotaLowHookAccountScopingTests.
  • [P1] swift test --filter UsageStoreAccountQuotaWarningTests.
  • [P1] swift test --filter UsageStoreSessionQuotaTransitionTests.

What I checked:

  • Synthetic quota baseline path: The hook-specific quota-low path tracks quota samples separately from notifications but does not establish the notification path’s synthetic-placeholder exclusion before recording a crossing baseline. (Sources/CodexBar/UsageStore+QuotaWarnings.swift:114, 1e0cf756039c)
  • Provider event limiting: HookEventType still classifies provider_unavailable as rate-limited even though provider outage emission is transition-driven, allowing a second outage after recovery to be suppressed within the limiter window. (Sources/CodexBarCore/Hooks/HookEvent.swift:21, 1e0cf756039c)
  • Unscoped refresh failure emission: The added refresh_failed emission is in the ordinary provider failure branch; the separately handled selected token-account and selected Codex visible-account result paths are not covered by this call. (Sources/CodexBar/UsageStore+Refresh.swift:1090, 1e0cf756039c)
  • Re-review continuity: The previous completed review raised these three defects at 14fc1f3. The subsequently listed commits are a main merge whose recorded conflict concerns settings persistence and a CLI hook-test status adjustment, with no identified repair to the affected runtime paths. (1e0cf756039c)
  • Real behavior proof: The PR body records an isolated CLI invocation that executes a configured hook and shows the child receiving the expected CODEXBAR event values and JSON stdin payload. (Sources/CodexBarCLI/CLIHooksCommand.swift:1, 1e0cf756039c)
  • Security hardening history: Branch commits added a narrow inherited-environment allowlist, redacted failure logging, bounded execution, payload limits, and stable hook-state invalidation. (Sources/CodexBarCore/Hooks/HookRunner.swift:1, 875fe8f0d7a5)

Likely related people:

  • steipete: Authored the branch’s latest hook hardening, execution stabilization, state invalidation, integration merges, and CLI status alignment. (role: recent area contributor; confidence: high; commits: 43cbcbc0127c, 875fe8f0d7a5, ff6fb83dd71e; files: Sources/CodexBarCore/Hooks/HookRunner.swift, Sources/CodexBar/UsageStore+Hooks.swift, Sources/CodexBarCLI/CLIHooksCommand.swift)
  • vincent-peng: Introduced the merged account-scoped quota-warning state that this branch now reuses for hook crossing history. (role: adjacent feature owner; confidence: high; commits: 7ccfd6c0c58f; files: Sources/CodexBar/UsageStore+QuotaWarnings.swift)
  • Zihao-Qi: Introduced the background-work configuration-isolation pattern reused by hook settings persistence. (role: adjacent feature owner; confidence: medium; commits: 5c6e6f99847d; files: Sources/CodexBar/SettingsStore+ConfigPersistence.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (40 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-15T02:16:15.947Z sha 0937096 :: needs changes before merge. :: [P2] Preserve argument boundaries in the hooks editor
  • reviewed 2026-07-15T05:48:20.762Z sha 0937096 :: needs changes before merge. :: [P2] Preserve argument boundaries in the hooks editor
  • reviewed 2026-07-15T06:59:41.452Z sha 0937096 :: needs changes before merge. :: [P2] Preserve argument boundaries in the hooks editor
  • reviewed 2026-07-15T08:10:49.162Z sha 0937096 :: needs changes before merge. :: [P2] Preserve exact argument boundaries in the hooks editor
  • reviewed 2026-07-15T09:23:15.822Z sha 0937096 :: needs changes before merge. :: [P2] Preserve exact argument boundaries in the hooks editor
  • reviewed 2026-07-15T10:38:45.202Z sha 0937096 :: found issues before merge. :: [P2] Preserve exact argument boundaries in the hooks editor
  • reviewed 2026-07-17T02:11:05.337Z sha ff6fb83 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-17T04:41:09.074Z sha 14fc1f3 :: needs changes before merge. :: [P2] Ignore synthetic quota windows before recording hook baselines | [P2] Remove provider transitions from the time-window limiter | [P2] Emit refresh failures from selected-account result paths

@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: 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".

Comment thread Sources/CodexBarCore/Hooks/HookRunner.swift Outdated
Comment thread Sources/CodexBar/UsageStore.swift Outdated
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Jul 9, 2026
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>
@jychp
jychp force-pushed the feature/external-event-hooks branch from b1a5c65 to 2882485 Compare July 9, 2026 04:41
…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>
@jychp

jychp commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed d7130ae addressing the review:

  • Minimal hook environment (allowlist + CODEXBAR_* only), so provider keys in env can't reach a hook.
  • Rate-limit only provider_unavailable / refresh_failed; quota events dedupe upstream (fixes the quota_low multi-threshold suppression).
  • Redacted failure logs (coarse reason, never stderr).
  • Rebased onto current main, so Fix visual settings refresh #1996 is included and nothing is reverted.
  • Added real-behavior proof (codexbar hooks test output + stdin JSON) to the PR description.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 9, 2026
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>
@jychp

jychp commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed fa11d2d addressing the remaining P2 items:

  • Quota hooks decoupled from notification preferences (detection runs when notifications OR a matching hook rule is enabled; only the notification post is gated on the notification setting). Gated on hasQuotaHookRule, so behavior is unchanged for users without a quota hook.
  • Editing hook rules no longer triggers a provider refresh: hook writes bump a separate hooksRevision instead of configRevision, preserving the Fix visual settings refresh #1996 refresh isolation.

Quota/notification transition suites and hooks tests are green.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

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>
@jychp

jychp commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed 247bcb7 fixing the remaining P2 (quota_low threshold contract):

  • quota_low hooks now run on a dedicated path and fire when usage crosses each rule's own threshold upward, independent of the notification thresholds. A thresholdless rule falls back to the provider's notification thresholds.
  • The notification path reverted to notification-only; hooks no longer piggyback on notification-threshold crossings.
  • Crossing selection extracted to QuotaLowHookThreshold.crossedRules (Core) with focused unit tests: own-threshold crossing, thresholdless fallback, and selecting only the newly-crossed rule (no re-fire of lower thresholds).

All hooks + quota/notification transition suites green; make lint clean.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 9, 2026
@clawsweeper clawsweeper Bot added status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 13, 2026
Address ClawSweeper's remaining P2 (UsageStore+Hooks.swift): quotaLowHookUsage
was keyed by provider/window/windowID only, so when several accounts share one
provider, one account's usage observations could suppress, clear, or re-arm
another account's quota_low threshold crossing.

Add a stable account discriminator (the unredacted account email, used only as
an in-memory key, never logged or forwarded) to a dedicated QuotaLowHookUsageKey.
Each account/lane now tracks its crossings independently. Adds a regression test
covering two accounts on one provider plus lane independence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jychp

jychp commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Pushed b14629b addressing the remaining P2 (account-scoped quota_low baselines):

  • quotaLowHookUsage is now keyed by a dedicated QuotaLowHookUsageKey that includes a stable account discriminator (the unredacted account email, used only as an in-memory key, never logged or forwarded to a hook). Separate accounts on one provider now track their quota_low crossings independently, so one account's usage can no longer suppress/clear/re-arm another's episode.
  • Added QuotaLowHookAccountScopingTests: two accounts on one provider stay independent, and windows/lanes stay distinct for a single account.

make lint clean; hooks + session-quota + Codex baseline suites green.

@clawsweeper

clawsweeper Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

jychp and others added 2 commits July 14, 2026 06:55
…keys

Refine the account discriminator for quota_low hook state (ClawSweeper P2):
keying on the account email alone let email-less or same-email accounts share a
baseline, so one account could suppress/clear/re-arm another's threshold crossing.

quotaHookAccountKey now composes the full provider identity (email, organization,
login method). Accounts without an email, or sharing one field, key independently
whenever any other identity field differs; only truly identical identities (the
same account) share a baseline. The pure composition is factored out and covered
by tests for email-less, same-email, and identical-identity cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t-hooks

# Conflicts:
#	Sources/CodexBar/UsageStore+QuotaWarnings.swift
@jychp

jychp commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Merged current main (which landed #2130's account-scoping). quota_low hook crossing history now reuses main's stable account discriminator via QuotaWarningStateKey (dropped the parallel composite key/helper). Quota hooks remain decoupled from notification preferences. make lint clean; hooks + quota/session/account-scoping suites green.

@clawsweeper

clawsweeper Bot commented Jul 14, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Jul 14, 2026

@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: ff6fb83dd7

ℹ️ 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 on lines +129 to +132
self.pruneQuotaLowHookUsage(
provider: provider,
accountDiscriminator: accountContext.discriminator,
keepingExtraWindowIDs: Set(extraWindows.map(\.id)))

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 Don't prune Claude hook lanes on missing extras

When a Claude snapshot temporarily omits extraRateWindows, extraWindows is empty and this prune call removes every tracked extra-window baseline. The notification path above explicitly treats an empty extras payload as non-authoritative, but the hook path loses the baseline; if the same Claude model window reappears already past a quota_low threshold, dispatchQuotaLowHooks sees it as the first sample and never emits the crossing. Only prune when an authoritative extras list is present.

Useful? React with 👍 / 👎.

Comment on lines +190 to +193
let revision = self.settings.configRevision
guard self.quotaLowHookConfigRevision != revision else { return }
self.quotaLowHookUsage.removeAll()
self.quotaLowHookConfigRevision = revision

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 Preserve baselines across unrelated config edits

Because settings.configRevision increments for provider/order/token-account changes as well as hook edits, this clears quota_low baselines after unrelated config changes. If usage was 40%, the user changes a provider setting, and the next refresh is 95%, the previous sample is discarded here and no quota_low hook runs despite crossing the rule threshold. Track a hook-specific revision or compare the hooks config instead of the global config revision.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 17, 2026
…t-hooks

# Conflicts:
#	Sources/CodexBar/Resources/de.lproj/Localizable.strings
#	Sources/CodexBar/Resources/es.lproj/Localizable.strings
#	Sources/CodexBar/Resources/fr.lproj/Localizable.strings
#	Sources/CodexBar/Resources/ja.lproj/Localizable.strings
#	Sources/CodexBar/Resources/ko.lproj/Localizable.strings
#	Sources/CodexBar/Resources/nl.lproj/Localizable.strings
#	Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
#	Sources/CodexBar/Resources/ru.lproj/Localizable.strings
#	Sources/CodexBar/Resources/sv.lproj/Localizable.strings
#	Sources/CodexBar/Resources/uk.lproj/Localizable.strings
#	Sources/CodexBar/Resources/vi.lproj/Localizable.strings
#	Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings
#	Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings

@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: 14fc1f32e0

ℹ️ 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 on lines +142 to +144
let current = rateWindow.usedPercent / 100
let previous = self.quotaLowHookUsage[key]
self.quotaLowHookUsage[key] = current

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 Ignore synthetic quota windows for quota-low hooks

When Claude web reports no live session, the primary window is a synthetic 0%-used placeholder (isSyntheticPlaceholder), which the notification path already skips. This hook path records that placeholder as the previous usage, so if the next real session sample appears already above a quota_low threshold it looks like a 0%→high crossing and runs the hook even though no real quota lane crossed the threshold.

Useful? React with 👍 / 👎.

Comment on lines +21 to +22
case .providerUnavailable, .refreshFailed:
true

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 Do not suppress a new outage after recovery

When a provider goes outage → normal → outage again within ten minutes, emitProviderStatusHooks correctly creates a second provider_unavailable transition, but marking this event as rate-limited causes the second transition to share the same limiter key and be dropped. Since provider_unavailable is already transition-driven, this hides real repeated outages after a recovery rather than just coalescing refresh storms.

Useful? React with 👍 / 👎.

Comment on lines +1068 to +1071
self.emitHook(
.refreshFailed,
provider: provider,
status: Self.refreshFailureHookStatus(error))

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 Emit refresh failures from account-scoped paths

This refresh_failed emission only runs in the unscoped provider failure path. Selected token-account and Codex visible-account failures are handled in UsageStore+TokenAccounts.applySelectedOutcome / applySelectedCodexVisibleAccountOutcome, which update errors but never call emitHook, so users relying on configured accounts will not receive refresh_failed hooks when the selected account refresh fails.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 17, 2026
steipete added 2 commits July 17, 2026 06:17
…t-hooks

# Conflicts:
#	Sources/CodexBar/SettingsStore+ConfigPersistence.swift
@steipete
steipete merged commit 8c89d79 into steipete:main Jul 17, 2026
14 of 15 checks passed
@steipete

Copy link
Copy Markdown
Owner

Merged as 8c89d7906506a7ec6cfabcf0dbb9e473a344d4c5 from exact head 1e0cf756039cb5f76b54e55feaa1e4e732425153.

Verification:

  • make check: passed. Locale validation: 22 locales / 1,192 keys. SwiftFormat: 0/1,521 files changed. SwiftLint strict: 0 violations across 1,520 files. Repository, docs, package, signing, and test-sharding gates also passed.
  • swift test --filter CLIHooksTests: 3/3 passed.
  • swift test --filter ProviderSettingsDescriptorTests: 37/37 passed.
  • Focused hook, dispatch, event-emission, usage-store, settings, and config tests: 65 tests across 6 suites passed.
  • Maintainer review: no remaining actionable findings after the final payload-status correction. Security review confirmed direct executable launch without a shell, bounded rule/argument/payload sizes, minimal forwarded environment, local-only settings ownership, opt-in gating, rate limiting/coalescing, and failure containment outside refresh completion.
  • Exact-head CI: all required checks passed in run 29557521620: lint, Linux arm64, Linux x64, macOS shards 0 and 1, rollup, and GitGuardian. Linux x64's first attempt hit a base-only PlatformGatingTests invocation-log flake; the unchanged exact-head rerun passed in 8m09s. Both macOS shards passed (30m00s and 29m20s).

Manual local hook test used an isolated config under /tmp/codexbar-pr2001-final-ff6fb83dd. The rule directly executed /usr/bin/tee and wrote stdin to /tmp/codexbar-pr2001-final-ff6fb83dd/event.json. With hooks disabled, the test command exited 1 and created no file. After enabling the same rule, simulated quota_reached dispatch succeeded and the file contained exactly:

{"event":"quota_reached","provider":"codex","timestamp":"2026-07-17T01:11:08Z","usagePercent":1,"window":"session"}

The missing-executable failure case also returned immediately without wedging the command or refresh path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: run an external command on quota/provider events

2 participants