Skip to content

fix(web): disable AI chat on the EU instance - #997

Merged
Makisuo merged 1 commit into
mainfrom
fix/disable-ai-chat-eu
Sep 23, 2026
Merged

Makisuo merged 1 commit into
mainfrom
fix/disable-ai-chat-eu

Conversation

@Makisuo

@Makisuo Makisuo commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Turns off Maple AI chat on the EU dashboard for now. One flag, aiChatEnabled = currentRegion !== "eu" in apps/web/src/lib/region.ts, gates every entry point:

  • header "Ask Maple AI" button
  • "Ask Maple AI" command palette action
  • the C shortcut (the global chat sheet never mounts)
  • the "fix with AI" action on a broken dashboard widget
  • /chat, which now redirects to / (covers old notification deep links)

US is unchanged.

Not covered: the EU API still forwards /api/chat/* to its AI Worker, and the follow-up chat inside investigations still works. Both can be gated separately if needed.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Availability
    • AI chat is now available only in supported regions. In regions where it isn’t enabled, the chat page, dashboard chat button, command-palette action, and AI-powered widget repair option are not available. Elsewhere, these features remain accessible as before.

Hide every chat entry point (header button, command palette action, C
shortcut, widget fix action) and redirect /chat to / when the dashboard
is built for the EU region.
@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c51f5d03-e112-4490-971d-11b54d76701c

📥 Commits

Reviewing files that changed from the base of the PR and between e69ad03 and 352d4bc.

📒 Files selected for processing (6)
  • apps/web/src/components/chat/global-chat-sheet.tsx
  • apps/web/src/components/command-palette/command-palette.tsx
  • apps/web/src/components/dashboard-builder/widgets/widget-actions-context.tsx
  • apps/web/src/components/layout/dashboard-layout.tsx
  • apps/web/src/lib/region.ts
  • apps/web/src/routes/chat.tsx
 ____________________________________________________________________
< Never go full rewrite. You don't buy that? Ask Netscape Navigator. >
 --------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

❤️ Share

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

@maple-review-bot

Copy link
Copy Markdown

Maple observability review: 96/100

Excellent · Observability looks complete · reviewed 352d4bc

Score Critical Warnings Notes Changes observable
96/100 0 0 2 2 of 3

PR #997 gates the AI chat feature off on the EU instance by hiding the chat entry points (header button, command palette action, global chat sheet, widget fix action) and redirecting /chat to / in the TanStack Router beforeLoad hook. The gating is a single client-side constant (aiChatEnabled in region.ts) and the change adds no observability at all: no client event, log, span, or metric is emitted when the feature is silently removed for EU users. All six hunks read; no unit the diff adds carries instrumentation, though the repo's existing web telemetry (web.vitals/web.performance) is unrelated to this feature's removal and needs no work here.

Findings

Severity Check Where Finding
Note MET-02 apps/web/src/lib/region.ts:27 Observability gap
Note STAT-04 apps/web/src/routes/chat.tsx:33 Observability gap
What to change

Observability gap (apps/web/src/lib/region.ts:27)

The change removes a user-facing feature (AI chat) from the EU instance by hiding or redirecting every entry point, but adds no telemetry: no client event, log, span attribute or metric. maple-web reports web.vitals and web.performance metrics to Maple; nothing on this new flag will appear. Six months from now, if EU users report that AI chat vanished, there will be no way to see from telemetry when the gate shipped, which region applied it, or whether any users still hit /chat redirects and were dropped on /.

In the existing maple-web meter idiom (e.g. web.vitals.ratings_total), add a monotonic counter such as web.chat.gate_total with attribute {maple.region: currentRegion, chat.action: "redirect" | "hidden"} — or at minimum log a single client event at module load when aiChatEnabled is false, so the gate's activation is visible in EU telemetry.

Observability gap (apps/web/src/routes/chat.tsx:33)

The /chat beforeLoad redirect covers old notification deep links but produces no trace signal that the redirect happened — the span for the /chat navigation and the redirect outcome carry no distinguishing attribute, and no event is logged. If EU traffic still arrives on /chat (which the redirect implies it will), this traffic is invisible to Maple.

Inside the beforeLoad hook, emit a client event (maple.client.event = "chat.redirect", maple.region = currentRegion) using the existing telemetry helpers in apps/web/src/lib/services/common/telemetry.ts so EU deep-link traffic shows up as a visible, measurable event.
What was reviewed
Change Kind Observable Evidence
aiChatEnabled flag (apps/web/src/lib/region.ts) feature flag yes single constant + comment; see telemetry convention in apps/web/src/lib/services/common/telemetry.ts
/chat redirect gate (apps/web/src/routes/chat.tsx) route gate yes TanStack Router beforeLoad redirect; all other entries are client-side UI gating
AI chat entry-point gating (header, palette, shortcut, sheet, widget fix) feature removal no no new spans, logs, events or metrics added by this diff

Score: 100, minus 25 per critical finding, 10 per warning and 2 per note. Check ids refer to Maple's instrumentation audit. Updated on every push.

@Makisuo
Makisuo merged commit e7bc2d9 into main Sep 23, 2026
39 of 40 checks passed
@Makisuo
Makisuo deleted the fix/disable-ai-chat-eu branch September 23, 2026 16:14
Makisuo added a commit that referenced this pull request Sep 23, 2026
A review pass gets `review_files`, an @effect-agent/capabilities Subagent
delegation. Past 12 reviewable files the prompt has it split the files
into groups of related files and delegate each group, in parallel, to a
`pr-review-worker` child: the parent's own read-only toolkit (the grant is
exactly those tools, depth one), 16 calls and 4 minutes per child, at most
8 children and 4 at a time, reserved from the parent's budget. The child
answers confirmed findings one per line; the parent verifies and files.

Verified with review:local on #997 with a forcing prompt: the child ran,
returned a finding, and the parent filed it through submit_review.

docs/pr-review-agent-plan.md now describes the built agent end to end,
including the GitHub App permissions and events it needs.
Makisuo added a commit that referenced this pull request Sep 23, 2026
…maple conversations and fixes (#1001)

* feat(pr-review): review pull requests for correctness, security and performance, not only observability

The reviewer becomes a general code review agent. Observability stays as
one lens with its maple-audit check ids.

- Findings carry a category; only observability findings need a check id.
- Verdict is clean | issues | not_applicable. A data migration rewrites
  stored reviews (gaps -> issues, instrumented -> clean, findings get
  category observability).
- A finding may carry `replacement`, the exact code for its line range,
  posted as a GitHub suggestion over that range.
- The prompt reads the repository's CLAUDE.md / AGENTS.md /
  .maple/review.md as rules and may read callers to confirm a bug.
- Config and test files are reviewed; call budget 3/file + 6 (8..60),
  pass ceiling 80 calls / 12 minutes.
- Check run renamed `Maple / review`.
- docs/pr-review-agent-plan.md gains the phase 1-5 plan.

* feat(pr-review): pr_context tool and a review:eval harness

pr_context: one call returns the pull request's commits, what people and
other bots already said on it (the App's own comments excluded) and the
head commit's checks, failing first. The prompt calls it once so a review
never repeats a thread or restates a CI failure. Wired through
GithubAppClient -> GithubProvider -> VcsSourceService, and answered from
`gh` in review:local.

review:eval: `mine` blames each fix: commit's changed lines back to the
squash-merged PR that wrote them; `run` reviews every curated case in
pr-review-eval/corpus.json and counts it caught when a finding lands within
three lines of what the fix changed. Seed corpus: five shipped bugs.

* feat(pr-review): follow findings across pushes, resolve fixed threads, debounce bursts

Phase 2 of the review agent.

- pr_review_findings stores every posted finding with a pull request-wide
  handle (F1, F2, ...), its inline comment id and open | resolved | dismissed.
- A later push's kickoff names the last reviewed head, the files changed
  since (GitHub compare) and the findings still open. submit_review returns
  `resolved` handles; the service replies "Fixed in <sha>" and resolves the
  thread through GraphQL resolveReviewThread.
- A new finding within three lines of an open or dismissed one in the same
  category is not posted again. A thread a person resolved, or answered
  "won't fix", marks its finding dismissed before the next review.
- The summary carries "Still open from earlier reviews" and "Fixed since the
  last review"; the score counts findings still open, not only new ones.
- A synchronize is debounced: the row is queued at once (so an older head is
  still superseded) and the start is re-enqueued with a 90 s delay; the
  delayed copy starts only a row no later push replaced. Fix verification
  ignores the delayed copy.
- Per-repository review settings (vcs_repositories.pr_review_config):
  instructions, ignored paths, categories, inline threshold, drafts. Stated
  in the kickoff and enforced on submit.

* feat(pr-review): answer @maple on pull requests, re-review on request, commit asked-for fixes

Phases 3 and 4 of the review agent.

Conversation:
- GitHub `issue_comment` and `pull_request_review_comment` webhooks map to a
  new `pull-request-comment` job only when a person (not a bot) mentions
  @maple on a pull request. VcsSyncService forwards it to an optional
  PullRequestCommentSink; apps/api binds PrReviewConversationService.
- Answered for OWNER / MEMBER / COLLABORATOR only, 100 answers per org per
  day, once per comment (pr_review_replies unique on repository + comment).
- Each answer is one turn of a new `pr-reply` agent on `<orgId>:prr-<id>`
  with the review's read-only tools, finishing on `submit_reply`. The
  thread it posts to (conversation or review-thread root) is bound on the
  row, never taken from the model. 👀 on receipt; a pass that ends without
  an answer says so on the pull request.
- `@maple review` re-reviews the head now: no debounce, drafts included,
  a head already reviewed is reviewed again (PrReviewService.reviewNow).

Fixes:
- `@maple fix` offers `propose_edit`: exact oldText -> newText edits staged
  in pr_review_edits. On submit they are applied to the files at the head
  and committed through the Git Data API as one commit, fast-forwarding
  the PR branch (never forced).
- Only for commenters with write/maintain/admin, only on the same
  repository's branch (never a fork or the default branch), never under
  .github/workflows or .git, at most 40 edits. A branch that moved since
  the head was read, or an edit that no longer applies, is reported
  instead of committed. Needs the App's `contents: write`.

* feat(pr-review): repository review settings, review history API, reaction-based precision

Phase 5 backend.

- GET/PUT /api/integrations/github/repositories/:id/pr-review/config reads
  and writes a repository's PrReviewRepositoryConfig (instructions, ignored
  paths, lenses, inline threshold, drafts, dailyLimit). Any member reads,
  admins write; input is trimmed and de-duplicated.
- GET /api/integrations/github/repositories/:id/pr-reviews lists the 50 most
  recent reviews with status, verdict, score and finding count.
- dailyLimit caps reviews per repository per UTC day under the org ceiling.
- The review-thread query now reads 👍 / 👎 on each inline comment. They are
  stored per finding (reactions_up/down) whenever threads are read: before a
  follow-up review and when the pull request closes, which also records
  last-minute dismissals. Totals land on the span as
  maple.pr_review.reactions_up / reactions_down, the live precision signal.

* feat(web): review settings and history for a repository's pull request reviews

A gear beside a repository's "Review PRs" switch (shown when the org has
the prreview flag and the repository has reviews on) opens a dialog:

- Settings: extra review rules, ignored paths, lenses, inline threshold,
  review drafts, daily limit. Loaded with githubGetPrReviewConfig, saved
  with githubSetPrReviewConfig; admins only, others see it read-only.
- Recent reviews: the 50 newest from githubListPrReviews with outcome,
  score, findings, time and a link to the review comment; refetches every
  5 s while one is queued or running.

* feat(pr-review): fan a large pull request out to child reviewers

A review pass gets `review_files`, an @effect-agent/capabilities Subagent
delegation. Past 12 reviewable files the prompt has it split the files
into groups of related files and delegate each group, in parallel, to a
`pr-review-worker` child: the parent's own read-only toolkit (the grant is
exactly those tools, depth one), 16 calls and 4 minutes per child, at most
8 children and 4 at a time, reserved from the parent's budget. The child
answers confirmed findings one per line; the parent verifies and files.

Verified with review:local on #997 with a forcing prompt: the child ran,
returned a finding, and the parent filed it through submit_review.

docs/pr-review-agent-plan.md now describes the built agent end to end,
including the GitHub App permissions and events it needs.

* fix(pr-review): accept quoted numbers and booleans in submit_review and review_files

The first Opus eval lost two of five runs to a strict decode: the model
sent line/endLine as "12" and coverage.instrumented as "true", and a
tool-argument decode failure ends the run. The submission schema is meant
to be lenient; numbers and booleans now accept their string forms and the
normalizer parses them (an unparseable line still drops the finding).

* fix(pr-review): accept null for any field a model leaves empty in submit_review

The second Opus eval lost two runs to checkId: null on findings outside
observability. Every submission field now accepts null as well as being
optional; the normalizer already treats both as absent.

* fix(pr-review): keep file modes in fix commits, and five review fixes

- commitFiles keeps an edited file's mode from the parent tree and refuses
  symlinks and submodules, so a fix to a script no longer drops its exec bit.
- A question or a negation in a finding's thread no longer dismisses it.
- `@maple review` while that head's review is queued or running reports it
  as started instead of failing as a duplicate.
- A failed read of a file being fixed stops the commit; only a 404 means
  the file is missing.
- review_files bounds its paths in prepareInput, so an oversized group is
  a returned failure instead of a decode error that ends the parent run.
- pr_context reads checks from the pull request's head, not the last
  commit on the first page.

* fix(pr-review): address review feedback and fix CI after merging main

CI:
- Merge main. pr_context declares its `phrases`, which main's
  McpToolOptions now requires.
- The four review migrations are regenerated on top of main's
  chat_workspace_credentials (they were never deployed): the verdict data
  migration, then one pr_review_agent schema migration.
- pr-review-local decodes each gh read at the boundary instead of
  returning unknown (effect-lint).
- dispatcher.test lists pr_context among the internal tools.
- run-tool-failures.test expects the tool-call `label` main's #1011 added;
  main fails the same test.

Review feedback:
- A mention inside a quote or fenced code no longer starts a command, so
  quoting "@maple fix" cannot trigger a commit. The reply agent still gets
  the whole comment.
- A reclaimed skipped or completed review clears skipReason and
  publishError.
- githubListPrReviews decodes rows with decodeAll, so one bad row is a
  typed persistence error, not a 500.
- review_files: the worker reports maintainability and observability check
  ids like the parent; `number` must be a positive integer; up to 12
  children.
- pr-review-local reads checks from the PR's head SHA.
- review:eval rejects a flag without a value and a non-integer --limit,
  keeps insertion-only fix hunks, and reports a location match as
  "located" rather than claiming the bug was caught.
- Settings dialog: controls are frozen while saving; review history keeps
  polling after a failed refresh.

* fix(pr-review): refuse a fix whose path passes through a symlink or submodule

commitFiles already refused a symlink or submodule as the edited file
itself; an intermediate segment of that kind was treated as a new file,
so the tree write would go through it or replace it. It is now refused
the same way.

* fix(pr-review): drop only unanchorable inline comments, read rules at the base

- Inline comments are checked against the pull request's diff hunks before
  the review is posted. A comment outside the diff (or on a file with no
  patch, or a range spanning two hunks) is dropped alone and noted in the
  review body; the summary comment still carries it. Before, one bad line
  was a 422 that threw away every inline comment and its thread tracking.
- The reviewer reads CLAUDE.md, AGENTS.md and .maple/review.md at the base
  SHA, so a pull request cannot rewrite the rules it is reviewed by.

* fix(pr-review): keep post-save edits, clear stale results on reclaim, and two eval/fan-out bounds

- Settings: a config refetch that lands after Save no longer replaces
  edits made since; it only resets fields the person left untouched.
- A reclaimed review clears reportJson and score, so the history never
  shows the previous run's result while the new one runs.
- review:eval writes located (not caught) in results.json, matching the
  summary.
- review_files rejects a PR number that is not a safe integer.
Makisuo added a commit that referenced this pull request Sep 24, 2026
…k on (#1052)

The EU instance shipped with AI off (#997) because model calls would carry
customer spans and logs to US providers. OpenRouter's in-region routing
closes that: requests to eu.openrouter.ai are decrypted and served only by
providers inside the EU, and a model with no EU provider is a 404 rather
than a hop to the US.

With MAPLE_REGION=eu, every OpenRouter call (chat, reviews, embeddings,
decisions) goes to https://eu.openrouter.ai/api/v1. The EU catalogue serves
none of the US defaults, so the EU instance defaults to openai/gpt-6-luna
for chat, triage and reviews. Jev has no EU provider, so the EU has no
decision model unless MAPLE_DECISION_MODEL is set; the triage route says so
without calling out, and the gate reads no verdict as "investigate".

Reverts the web gating from #997 so every chat entry point is back on EU.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant