Skip to content

Manage environments and environment notifications per project - #2711

Open
ejsmith wants to merge 10 commits into
mainfrom
feature/environment-management
Open

ejsmith wants to merge 10 commits into
mainfrom
feature/environment-management

Conversation

@ejsmith

@ejsmith ejsmith commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Manage a project's environments under Project settings → Environments, and stop lower environments from sending notifications.

What you get

Setting Default Effect
Environment list empty Add names ahead of time; each has a Notify toggle. Matched case-insensitively, always shown in the casing you entered.
Discard unlisted environments off Drop events from environments not on the list.
Discard events without an environment off Only applies while unlisted environments are discarded.
Notify other environments on Notifications for unlisted environments and events without one.

With the defaults, nothing changes from today.

Ingestion

flowchart LR
    E[Event] --> L{Environment<br/>listed?}
    L -- yes --> K[Keep]
    L -- no --> D{Discard<br/>unlisted?}
    D -- no --> K
    D -- yes --> X[Discard and count the name]
Loading

Clients get a 202, so discards would be invisible. The page lists every discarded name with a count and an Allow button. Discarded events don't count toward the plan limit.

Notifications (email, Slack, webhooks)

flowchart LR
    A[Event] --> N{Environment<br/>notifies?}
    N -- no --> H[Hold: stack stays pending]
    N -- yes --> P{New or regression<br/>pending?}
    P -- yes --> C[Send once]
    P -- no --> R[Usual rules]
Loading

Example: a bug appears in dev and gets no notification. The day it reaches production, one "new error" notification goes out. Regressions work the same way.

Deploy notes

  • Names are identifiers: 1–64 ASCII letters, digits, ., _ or -, starting with a letter or digit (e.g. staging-eu). Anything else, including spaces, is treated as no environment. This tightens Add event environments and filtering #2570, which hasn't been released.
  • No reindex or migration. The new fields aren't mapped.
  • Existing stacks count as already notified, so the deploy causes no notification flood.
  • Rolling deploys: while both versions run, an old node that saves a whole project or stack drops the new fields.
  • Subjects: email and Slack subjects now name the environment, e.g. "New error in Production".

API

POST, PATCH and DELETE /api/v2/projects/{id}/environments · PUT …/environments/settings · GET …/environments/discarded

Testing

  • Backend: full suite, 3,654 passed.
  • Frontend: validate, 1,195 unit tests, build.
  • End-to-end: a new test covers the Environments page.
  • Mutation checks: each fix was broken on purpose, and a test failed every time. The fixes covered are Slack escaping, notification claim races, regression state per batch, and the discarded-name overflow.

🤖 Generated with Claude Code

Projects can list their environments, discard events from environments that are
not listed, and choose which environments send notifications.

Project settings: a list of named environments (stored casing, Notify toggle,
max 100, case-insensitive duplicates rejected), plus DiscardUnlistedEnvironments,
DiscardEventsWithoutEnvironment and NotifyOtherEnvironments (default true).
Defaults keep today's behavior.

Discarding happens in a new pipeline action before stacking and uses the existing
IsDiscarded path, so it is counted as discarded usage like stack discards. Rejected
names are recorded in the cache per project (first-seen casing, per-day counts,
last seen, capped, 7 day expiry) and exposed through the API.

Notifications: every channel requires a notifying environment. New notifications
use a new Stack.IsNewNotificationPending flag that is claimed atomically in
Elasticsearch, so a stack first seen in a non-notifying environment is reported as
new the first time it reaches a notifying one. Stacks created before the flag
never notify as new. Notification emails and Slack messages mention the
environment. Daily summaries are unchanged.

New project endpoints manage the list and settings, and the Svelte project
settings gain an Environments page. Listed casing is used in the environment
filter, event details, event and session tables.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 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-11-03T00:19:04.202632Z 6264e61 New commits
ℹ️ 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.

@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: 38228a850a

ℹ️ 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".

ejsmith and others added 3 commits November 2, 2026 16:08
Escape client-controlled text in Slack messages. Environment names, project
names and the notification type could mention @channel or inject links.

Guard against a stale whole-document stack save restoring a claimed
notification: a successful Elasticsearch claim also takes a per-stack cache
key, so a restored flag is cleared again without notifying twice. The ingestion
paths that saved the whole stack (tags and regressions) now patch only the
fields they change.

Regressions follow the same rule as new stacks. A regression seen only in a
non-notifying environment is reported by the first notifying occurrence, and a
status change drops anything still pending. Notifying environments behave as
before.

Names beyond the discarded-environment cap are no longer silently skipped: the
page reports how many events came from how many other names. Matching and keys
now share one normalization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adding an observed environment now pre-fills the add form instead of saving the
lowercase aggregation key, so the casing can be confirmed or corrected first.

Turning on discarding of unlisted environments asks for confirmation when no
environment is listed or none of the listed ones has had recent events. The
discarded table reports events from names beyond the tracked limit.

The add form uses the generated project environment schema, the filter trigger
and event cell titles show the listed casing, and the change-environment
messages are named like their siblings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Key the regression claim guard by regression cycle (the fix date), so a stack
that regresses again is never blocked by the previous cycle's claim while the
new pending flag is already visible in Elasticsearch. The key is no longer
removed after patching.

Route every status change through Stack.SetStatus, including the duplicate stack
merge in CleanupOrphanedDataJob and FixDuplicateStacks, so merged stacks drop
pending notifications too.

Add a test that drives AssignToStackAction with a stack another batch claimed
after it was loaded, so reverting the tags update to a whole-document save fails.
Update tags for several stacks in parallel and document at-most-once delivery
when the guard cannot be written. Reword the overflow row of the discarded
table so it says what users can actually do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@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: 03eaf9def5

ℹ️ 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".

Events with an explicit stack id each load their own copy of the stack, so a regression first seen in a non-notifying environment left later production events in the same batch without the pending regression state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

ℹ️ 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".

ejsmith and others added 3 commits November 2, 2026 16:52
An immediate regression now reserves the same per-cycle key a pending claim takes, so a concurrent batch that marks the regression pending from a non-notifying environment can't send it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Put the environment table first, add environments through a dialog like the other project settings pages, move the other-environments notification toggle into the table, replace the discard switches with an "Accept events from" choice, and only show discarded names when they matter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Cancel button inside the add-environment form defaulted to a submit button, so cancelling added the typed name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

ℹ️ 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 +20 to +23
await test.step('observed environments appear as unlisted rows', async () => {
await page.goto(`/project/${projectId}/environments`);

await expect(page.getByRole('button', { name: 'Add Environment' })).toBeVisible();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add explicit Arrange–Act–Assert sections to the browser journey

The new browser test omits the required explicit // Arrange, // Act, and // Assert sections throughout its scenarios. Named test.step blocks do not satisfy the ClientApp test-structure requirement, and several steps interleave assertions with subsequent actions. Add the required sections within each scenario, repeating them for multi-step interactions where needed.

AGENTS.md reference: src/Exceptionless.Web/ClientApp/AGENTS.md:L27-L27

Useful? React with 👍 / 👎.


namespace Exceptionless.Tests.Extensions;

public class ProjectEnvironmentExtensionsTests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Inherit the required base for isolated C# tests

This new test class does not inherit a repository test base. Root AGENTS.md explicitly requires TestWithLoggingBase for isolated C# tests; these extension-method tests fit that category. Use the existing ITestOutputHelper constructor and base-class pattern used by neighboring extension tests so this suite follows the required fixture convention.

AGENTS.md reference: AGENTS.md:L53-L53

Useful? React with 👍 / 👎.

Names are now 1 to 64 ASCII letters, digits, periods, underscores, or hyphens that start with a letter or digit, so they can be used in filters and scripts. Invalid names, including ones with spaces, stay unspecified instead of being rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@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: 6264e61a1e

ℹ️ 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".

return String.IsNullOrEmpty(name) || name.Length > 64 || name.Any(Char.IsControl)
? null
: name;
return name is not null && EnvironmentNameRegex().IsMatch(name) ? name : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve previously valid environments when reading stored events

BLOCKER: For existing events with previously valid names such as Staging West, prod/eu, or produção, this stricter normalization also runs during deserialization, not just when adding managed environments. EventEnvironmentConverter.Read uses this method and now moves those stored values into custom data instead of populating Environment, so fetching historical events changes their public API representation and makes their environment disappear from event details. Preserve the previous read contract for persisted events, even if newly listed names must follow stricter rules, and cover a historical-record round trip.

AGENTS.md reference: AGENTS.md:L74-L75

Useful? React with 👍 / 👎.

Environment names are identifiers now, so the filter no longer offers qa,east.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Exceptionless.AppHost 23% 22% 130 ❌
Exceptionless.Core 79% 71% 11155 ✔
Exceptionless.Insulation 51% 43% 370 ➖
Exceptionless.Web 87% 72% 9547 ✔
Summary 81% (29197 / 35977) 70% (14633 / 20762) 21202 ✔

This branch has not been deployed

No deployments
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