Skip to content

fix(signals): report the PR quota pause and unblock suggestion scans - #94327

Merged
trunk-io[bot] merged 1 commit into
masterfrom
posthog/signals-pr-quota-scout-pause
Sep 8, 2026
Merged

trunk-io[bot] merged 1 commit into
masterfrom
posthog/signals-pr-quota-scout-pause

Conversation

@posthog

@posthog posthog Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Problem

A project over its self-driving pull request limit loses its scout suggestion scans, which never open a pull request and never charge that limit.

  • Scout runs pause at the same limit and nothing records it. run_signals_scout_activity and the manual run endpoint read the raw quota limiter, so the pause emits no signal_report_quota_paused event and ignores the self-driving-quota-enforcement kill switch. Every other paused stage reports itself.
  • The scouts tab then blames the wrong limit. The Daily report limit row read "Paused by plan limit" over its own count, and both 429 bodies named a "Signals credits quota" that appears nowhere in the UI.
  • A skipped suggestion scan costs a week. The coordinator stamps last_requested_at at dispatch, so the project waits the full 7-day refresh window even after its billing period resets.

Closes #94166

Changes

  • Suggestion scans now run for a project over its pull request limit, on the schedule and on manual refresh. The daily refresh cap, the AI data processing consent gate, and the kill switch all stay.
  • Scout runs still pause at the limit, but the pause now emits signal_report_quota_paused with stage="scout_run" and blocks only when the enforcement flag is on.
  • The pre-dispatch gate captures that event itself. A rejected trigger dispatches nothing, so it never reaches the activity that would otherwise report the pause.
  • The Daily report limit row always shows its own count now. Over-quota it read "Paused by plan limit"; it now reads "3 / 10 today".
  • The usage widget banner names what actually pauses: "Agents are paused. They won't write new reports or open pull requests until the limit is raised or usage resets."
  • Both 429 bodies and the run endpoint's OpenAPI descriptions speak the product's pull request vocabulary.
  • Mechanical: select_related("organization") at the two spend-gate callers for the new capture, plus the regenerated OpenAPI descriptions in products/signals/frontend/generated/api.ts and services/mcp/src/generated/signals/api.ts.

No screenshot: the changed label only renders for a quota-limited organization and no story covers that state. Both strings are quoted above.

Note

Whether an over-quota project should run scouts at all stays open, and this PR does not decide it. The gate above scout runs is kept because ingestion still drops those runs' signals at the same limit (check_signals_quota_limited_activity), so lifting it alone would spend model budget on reports that never arrive. Lifting it properly means lifting the ingestion, promotion, and summary gates too, which is a spend decision for a human.

How did you test this code?

  • test_activity_skips_run_attributed_to_the_limit_that_fired gains a dark-launch case: a limited project with enforcement off runs, and still reports the pause. The old test read a bare boolean, so it could not express that state.
  • test_run_over_quota_reports_the_pause_and_blocks_only_when_enforced replaces the old 429 test. It guards the event trail this PR is about: drop the capture from check_spend_gates and a rejected manual run leaves no record again.
  • Two suggestion tests patch products.signals.backend.quota.is_team_limited, the limiter read both quota helpers bottom out on. Re-adding a quota gate to the scan in any form fails them.
  • Ran locally: the six signals suites covering these paths, posthog/temporal/tests/ai/test_module_integrity.py, hogli lint:tach, hogli ci:preflight --fix (no failures), and repo-wide uv run mypy --cache-fine-grained . (clean).
  • Not run: no manual click-through, and no Storybook render. Building the @posthog/quill workspace was needed before tsgo could resolve this component's imports at all; after that, products/signals typechecks with zero errors.

Automatic notifications

  • Publish to changelog?

Docs update

products/signals/ARCHITECTURE.md gains the scout_run stage name in the gate table and a paragraph on why suggestion scans are deliberately not gated. products/signals/backend/scout_harness/AGENTS.md drops "signals quota" from the arun_scout_suggestions gate list.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored by Claude Code (Opus 5) in a PostHog Desktop cloud task, acting on the linked inbox report. The PR is unassigned because gh api user returns 403 in this environment, so the driver's GitHub handle could not be verified, and this repo's rules forbid guessing one.

Skills invoked: /writing-user-facing-copy for the four copy strings, /writing-tests for the test gate, /writing-pr-descriptions for this body.

The report's stated solution named two remedies that pull apart for scout runs: route them through the shared gate, and drop the check from paths that mint no pull requests. The split shipped here is by whether a path can reach a pull request at all. A suggestion scan cannot, so it loses the check. A scout run feeds the pipeline that does, so it keeps the check and gains the telemetry and the kill switch. Un-gating scout runs was considered and rejected for the reason in the note above; that is the one open question left for a reviewer.

Public artifact: nothing in the diff, the commit message, or this description carries material from the agent session. The report drew on a Discord thread and issue #94166; only the issue is referenced, by number.


Created with PostHog Desktop from an inbox report

The self-driving credits meter charges per shipped pull request, but the
scout paths read the raw quota limiter instead of the shared gate, so a
paused scout run emitted no `signal_report_quota_paused` event and ignored
the `self-driving-quota-enforcement` kill switch.

Route both scout paths (the scheduled activity and the pre-dispatch gate
behind the manual and workflow triggers) through `self_driving_quota_gate`
so the pause reports itself at `stage="scout_run"` and honors the flag.

Drop the quota check from scout suggestion scans: they open no pull
request, so they charge nothing, and a skip still burned the team's whole
refresh window because `last_requested_at` is stamped at dispatch.

Rewrite the copy in the product's pull request vocabulary, and stop the
quota pause overwriting the unrelated Daily report limit value.

Generated-By: PostHog Desktop
Task-Id: e426549b-2599-491f-9b67-1b8052418dfe
@trunk-io

trunk-io Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Complexity (TypeScript) — 1 function above the limit (max 19)

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

Function Location Complexity Limit
InboxUsageWidget products/signals/frontend/inbox/components/shell/InboxUsageWidget.tsx:148 19 10
✅ Bundle size — 🟢 -121.4 KiB (-0.2%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 68.28 MiB · 🟢 -121.4 KiB (-0.2%)

File Size Δ vs base
posthog-app/_parent/products/workflows/frontend/Workflows/WorkflowScene.js 35.9 KiB 🟢 -89.1 KiB (-71.3%)
posthog-app/_parent/products/logs/frontend/LogsScene.js 46.1 KiB 🟢 -48.6 KiB (-51.3%)
posthog-app/_parent/products/data_catalog/frontend/DataCatalogScene.js 24.5 KiB 🟢 -6.6 KiB (-21.2%)
posthog-app/src/scenes/project-homepage/ProjectHomepage.js 37.4 KiB 🔺 +5.3 KiB (+16.6%)
render-query/src/render-query/render-query.js 21.87 MiB 🔺 +4.0 KiB (+0.0%)
posthog-app/src/scenes/experiments/Experiment.js 264.9 KiB 🔺 +3.3 KiB (+1.3%)
posthog-app/_parent/products/signals/frontend/inbox/InboxScene.js 760.9 KiB 🟢 -3.2 KiB (-0.4%)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.46 MiB 🔺 +2.0 KiB (+0.1%)
posthog-app/_parent/products/data_catalog/frontend/DataCatalogMetricScene.js 23.0 KiB 🔺 +1.6 KiB (+7.3%)
exporter/src/queries/schema.js 1.20 MiB 🔺 +1.5 KiB (+0.1%)
posthog-app/src/queries/schema.js 1.20 MiB 🔺 +1.5 KiB (+0.1%)
posthog-app/src/scenes/App.js 38.2 KiB 🔺 +1.2 KiB (+3.1%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.45 MiB · 22 files 🔺 +317 B (+0.0%) ███░░░░░░░ 32.3% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.89 MiB · 3,268 files 🔺 +4.4 KiB (+0.0%) █████████░ 91.5% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
307.3 KiB ../node_modules/.pnpm/posthog-js@1.425.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
272.4 KiB ../node_modules/.pnpm/posthog-js@1.425.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
255.2 KiB src/taxonomy/core-filter-definitions-by-group.json
154.2 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.4 KiB src/lib/api.ts
95.8 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.28 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.28 MiB · 18 files 🔺 +171 B (+0.0%) ████░░░░░░ 39.8% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 45 files 🔺 +124 B (+0.0%) n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
754.1 KiB dist/toolbar/toolbar-app-HCAVZOPH.css
599.5 KiB dist/toolbar/chunk-chunk-IP5OG3UU.js
484.7 KiB dist/toolbar/chunk-chunk-EL6RQSMS.js
135.6 KiB dist/toolbar/chunk-chunk-7UG5SHZR.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
71.3 KiB dist/toolbar/toolbar-app-P2YIMAVU.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-TX5RGCWJ.js
21.0 KiB dist/toolbar/chunk-chunk-6WYJMFDF.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🟢 -1.02 MiB (-0.1%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1458.81 MiB · 🟢 -1.02 MiB (-0.1%)

ℹ️ MCP UI apps size — 33 app(s), 17665.2 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 600.0 KB 195.2 KB
action 458.2 KB 195.2 KB
action-list 564.9 KB 195.2 KB
cohort 457.2 KB 195.2 KB
cohort-list 563.8 KB 195.2 KB
email-template 457.0 KB 195.2 KB
error-details 472.9 KB 195.2 KB
error-issue 457.9 KB 195.2 KB
error-issue-list 564.7 KB 195.2 KB
experiment 562.0 KB 195.2 KB
experiment-list 565.6 KB 195.2 KB
experiment-results 567.1 KB 195.2 KB
feature-flag 567.6 KB 195.2 KB
feature-flag-list 571.4 KB 195.2 KB
feature-flag-testing 461.4 KB 195.2 KB
inline-scan 457.7 KB 195.2 KB
insight-actors 563.0 KB 195.2 KB
invite-email-preview 456.4 KB 195.2 KB
llm-costs 560.0 KB 195.2 KB
session-recording 459.0 KB 195.2 KB
survey 458.8 KB 195.2 KB
survey-global-stats 562.7 KB 195.2 KB
survey-list 565.5 KB 195.2 KB
survey-stats 562.7 KB 195.2 KB
trace-span 457.6 KB 195.2 KB
trace-span-list 564.7 KB 195.2 KB
vision-observation-list 563.9 KB 195.2 KB
workflow 457.5 KB 195.2 KB
workflow-list 564.2 KB 195.2 KB
loops-review 461.9 KB 195.2 KB
query-results 756.3 KB 195.2 KB
render-ui 839.5 KB 195.2 KB
visual-review-snapshots 462.0 KB 195.2 KB
ℹ️ ClickHouse migration SQL — 1 migration(s)

ClickHouse migration SQL per cloud environment

  • unset
    • all
      CREATE TABLE IF NOT EXISTS sharded_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/posthog.sharded_billing_usage_records', '{replica}', inserted_at)
      PARTITION BY toYYYYMM(timestamp)
      ORDER BY (team_id, toDate(timestamp), producer_id, usage_key, record_id)
      CREATE TABLE IF NOT EXISTS billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = Distributed('posthog', 'posthog_test', 'sharded_billing_usage_records', cityHash64(team_id))
      CREATE TABLE IF NOT EXISTS writable_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = Distributed('posthog', 'posthog_test', 'sharded_billing_usage_records', cityHash64(team_id))
      CREATE TABLE IF NOT EXISTS kafka_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
      )
      ENGINE = Kafka(warpstream_shared, kafka_topic_list = 'clickhouse_billing_usage_records_test', kafka_group_name = 'clickhouse_billing_usage_records', kafka_format = 'JSONEachRow')
      SETTINGS date_time_input_format = 'best_effort'
      CREATE MATERIALIZED VIEW IF NOT EXISTS billing_usage_records_mv
      TO writable_billing_usage_records
      AS SELECT
          schema_version,
          record_id,
          producer_id,
          team_id,
          organization_id,
          usage_key,
          unit,
          quantity,
          timestamp,
          inserted_at,
          _timestamp,
          _offset,
          _partition
      FROM kafka_billing_usage_records
  • US, EU, DEV
    • data
      CREATE TABLE IF NOT EXISTS sharded_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/posthog.sharded_billing_usage_records', '{replica}', inserted_at)
      PARTITION BY toYYYYMM(timestamp)
      ORDER BY (team_id, toDate(timestamp), producer_id, usage_key, record_id)
      CREATE TABLE IF NOT EXISTS billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = Distributed('posthog', 'posthog_test', 'sharded_billing_usage_records', cityHash64(team_id))
    • small
      CREATE TABLE IF NOT EXISTS writable_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
        
      , _timestamp DateTime
      , _offset UInt64
      , _partition UInt64
      
      )
      ENGINE = Distributed('posthog', 'posthog_test', 'sharded_billing_usage_records', cityHash64(team_id))
      CREATE TABLE IF NOT EXISTS kafka_billing_usage_records
      (
          schema_version UInt8,
          record_id String,
          producer_id LowCardinality(String),
          team_id Int64,
          organization_id UUID,
          usage_key LowCardinality(String),
          unit LowCardinality(String),
          quantity Int64,
          timestamp DateTime64(6, 'UTC'),
          inserted_at DateTime64(6, 'UTC')
      )
      ENGINE = Kafka(warpstream_shared, kafka_topic_list = 'clickhouse_billing_usage_records_test', kafka_group_name = 'clickhouse_billing_usage_records', kafka_format = 'JSONEachRow')
      SETTINGS date_time_input_format = 'best_effort'
      CREATE MATERIALIZED VIEW IF NOT EXISTS billing_usage_records_mv
      TO writable_billing_usage_records
      AS SELECT
          schema_version,
          record_id,
          producer_id,
          team_id,
          organization_id,
          usage_key,
          unit,
          quantity,
          timestamp,
          inserted_at,
          _timestamp,
          _offset,
          _partition
      FROM kafka_billing_usage_records

@trunk-io

trunk-io Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@andrewm4894
andrewm4894 marked this pull request as ready for review September 4, 2026 10:46
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-04T10:55:29.675628Z 456ea69 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team September 4, 2026 10:48

@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: 456ea69b8d

ℹ️ 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 +109 to +110
if quota_gate.limited:
capture_signal_report_quota_paused(team, report_id=None, stage="scout_run", enforced=quota_gate.enforced)

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 only one dark-launch pause per scout run

When a team is quota-limited but enforcement is disabled, this preflight captures signal_report_quota_paused and permits the manual or workflow-triggered run to dispatch. run_signals_scout_activity() then captures the same stage="scout_run", report_id=None, enforced=false event again in scout_scheduler.py:104-105, so off-schedule dark-launch runs count twice while scheduled runs count once. This skews the would-block telemetry used to evaluate rollout; emit from the preflight only when the run will not reach the activity, or otherwise deduplicate the observations.

Useful? React with 👍 / 👎.

Comment on lines +290 to 292
{dailyReportLimitReached && (
<p className="text-xs font-medium text-danger mb-0 px-2.5 pb-1.5">
Daily report limit reached. New reports resume at midnight in your project's timezone.

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 Avoid promising a midnight resume while quota-paused

When a project has reached both its daily report cap and its enforced pull-request quota, removing the !quotaLimited condition shows “New reports resume at midnight,” but report generation remains blocked until the billing limit is raised or resets. InboxUsageWidget.tsx:221-225 now states that latter condition on the same Settings and agent-setup surfaces, leaving users with contradictory recovery guidance. Keep the quota-aware condition or describe only the daily counter reset without promising that reports resume.

Useful? React with 👍 / 👎.

if daily_gate.limited:
capture_signal_report_daily_limit_paused(team, report_id=None, stage="scout_run", gate=daily_gate)
if quota_limited:
if quota_gate.enforced:

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 Keep signal scouts gated until ingestion honors the flag

For a quota-limited team with enforcement disabled—or when the enforcement-flag lookup fails open—this condition now lets a signal-channel scout perform its full model-backed run. Its findings still flow through check_signals_quota_limited_activity() in temporal/buffer.py:86-105, which checks the raw quota limiter without the enforcement flag and drops the entire batch. These scouts therefore consume agent compute and can report successful runs while producing no inbox output; retain the raw scout guard until ingestion uses the same gate, or migrate both stages together.

AGENTS.md reference: products/signals/backend/scout_harness/AGENTS.md:L3-L3

Useful? React with 👍 / 👎.

@andrewm4894

Copy link
Copy Markdown
Member

/trunk merge

1 similar comment

Copy link
Copy Markdown
Member

/trunk merge

@trunk-io
trunk-io Bot merged commit bbc6360 into master Sep 8, 2026
510 of 518 checks passed
@trunk-io
trunk-io Bot deleted the posthog/signals-pr-quota-scout-pause branch September 8, 2026 13:02
@deployment-status-posthog

deployment-status-posthog Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-08 13:36 UTC Run
prod-us ✅ Deployed 2026-09-08 13:59 UTC Run
prod-eu ✅ Deployed 2026-09-08 14:03 UTC Run

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.

feat(signals): drop the Signals credits quota gate from scout suggestion scans

1 participant