Skip to content

Release v1.17.0#836

Merged
tphoney merged 18 commits intomainfrom
copybara/v1.17.0
Mar 12, 2026
Merged

Release v1.17.0#836
tphoney merged 18 commits intomainfrom
copybara/v1.17.0

Conversation

@github-actions
Copy link

@github-actions github-actions bot commented Mar 12, 2026

Copybara Sync - Release v1.17.0

This PR was automatically created by Copybara, syncing changes from the overmindtech/workspace monorepo.

Original author: David Schmitt (david.schmitt@overmind.tech)

What happens when this PR is merged?

  1. The tag-on-merge workflow will automatically create the v1.17.0 tag on main
  2. This tag will trigger the release workflow, which will:
    • Run tests
    • Build and publish release binaries via GoReleaser
    • Upload packages to Cloudsmith

Review Checklist

  • Changes look correct and match the expected monorepo sync
  • Tests pass (see CI checks below)

getinnocuous and others added 18 commits March 12, 2026 15:43
<img width="1156" height="155" alt="Screenshot 2026-03-09 at 12 56 47"
src="https://github.com/user-attachments/assets/74add57d-2df6-429c-b620-4114bf2fb60e"
/>
<img width="1446" height="1638" alt="image"
src="https://github.com/user-attachments/assets/e457e9c3-711f-4f4e-98d3-bdbf6ef7ec5c"
/>

Verified the full flow end-to-end via cURL — sending a signed Webflow
webhook payload to a local api-server successfully enqueues the River
job and sends a broadcast email via Resend as expected.

The feature is gated by config: if `WEBFLOW_WEBHOOK_SECRET` and
`RESEND_API_KEY` are not set, the handler and worker simply don't
register. This means the code can ship safely now. To go live we need:

- Add Webflow + Resend credentials to 1Password
([ENG-3043](https://linear.app/overmind/issue/ENG-3043/add-webflow-webhook-credentials-to-1password-global-vault),
[ENG-3044](https://linear.app/overmind/issue/ENG-3043/add-webflow-webhook-credentials-to-1password-global-vault))
- Configure the Webflow webhook URL to point at the production
api-server
- Import verified user emails into the Resend
[segment](https://resend.com/audience?segmentId=e562dcde-600f-4535-bdfc-5e72c5a16c3d)
([ENG-2957](https://linear.app/overmind/issue/ENG-2957/manual-csv-sync-of-verified-user-emails-to-resend))
- Automate adding new users to changelog notify [segment in
resend](https://resend.com/audience?segmentId=e562dcde-600f-4535-bdfc-5e72c5a16c3d)
[ENG-2958](https://linear.app/overmind/issue/ENG-2958/add-resend-contact-creation-to-user-signup-flow)
- Once those are done, changelog publishes in Webflow will automatically
trigger broadcast emails.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces a new externally reachable webhook endpoint and outbound
email-sending worker; while gated by config and protected with HMAC +
timestamp checks, mistakes could lead to unwanted job enqueues or email
broadcasts.
>
> **Overview**
> Adds a Webflow webhook integration that, when enabled via config,
verifies `X-Webflow-Signature` (HMAC-SHA256) and timestamp tolerance,
filters events by CMS collection ID, and enqueues a River
`ChangelogEmail` job.
>
> Adds a River worker that renders a new embedded HTML template and uses
the `resend-go` client to send a broadcast email to a configured Resend
segment; wiring includes new CLI/env config fields with secret redaction
and new ExternalSecret entries for Webflow/Resend credentials.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b1ac7f7b0e9605a99f8828d52942cd127515a8f2. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 782a27f26097b36ae2b053b9949ef49a962df666
…(#4193)

Pin all runtime Alpine base images from 3.23 to 3.23.3 (ships zlib
1.3.2-r0) and add apk upgrade --no-cache to builder stages using
golang:1.26-alpine, node:24-alpine, and nats:alpine where the base
Alpine version cannot be pinned directly.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to Docker base image patch pinning and
package upgrades, but they can affect container build reproducibility
and potentially introduce subtle runtime/library differences.
>
> **Overview**
> Pins all Alpine runtime stages from `alpine:3.23` to `alpine:3.23.3`
across Go-based services/sources (and multi-stage images like
`api-server`, `gateway`, `revlink`, `srcman`, etc.).
>
> Adds `apk upgrade --no-cache` to builder/base stages that inherit
Alpine indirectly (`golang:1.26-alpine`, `node:24-alpine`, and
`nats:alpine`) before installing required packages, ensuring patched
system libraries (e.g., zlib) are picked up during image builds.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
a8089e8113c4afcbc923f40d9de442f4227fac31. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 77fdaa992e127a8accaaa2328be36977e9c616fe
…(#4145)

📹
https://www.loom.com/share/4049912c73734143a8dde39ebf3f4fe6
📹

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[github.com/auth0/go-jwt-middleware/v2](https://redirect.github.com/auth0/go-jwt-middleware)
| `v2.3.1` → `v3.0.0` |
![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fauth0%2fgo-jwt-middleware%2fv2/v3.0.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fauth0%2fgo-jwt-middleware%2fv2/v2.3.1/v3.0.0?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/370) for more information.

---

### Release Notes

<details>
<summary>auth0/go-jwt-middleware
(github.com/auth0/go-jwt-middleware/v2)</summary>

###
[`v3.0.0`](https://redirect.github.com/auth0/go-jwt-middleware/blob/HEAD/CHANGELOG.md#v300-2026-01-19)

[Compare
Source](https://redirect.github.com/auth0/go-jwt-middleware/compare/v2.3.1...v3.0.0)

[Full
Changelog](https://redirect.github.com/auth0/go-jwt-middleware/compare/v2.3.1...v3.0.0)

**BEFORE YOU UPGRADE**

- This is a major release that includes breaking changes. Please see
[MIGRATION\_GUIDE.md](MIGRATION_GUIDE.md) before upgrading. This release
will require changes to your application.

##### Added

- Pure options pattern for validator, middleware, and JWKS provider
([#&#8203;357](https://redirect.github.com/auth0/go-jwt-middleware/pull/357),
[#&#8203;358](https://redirect.github.com/auth0/go-jwt-middleware/pull/358),
[#&#8203;360](https://redirect.github.com/auth0/go-jwt-middleware/pull/360))
- DPoP (Demonstrating Proof-of-Possession) support per RFC 9449
([#&#8203;363](https://redirect.github.com/auth0/go-jwt-middleware/pull/363))
- Framework-agnostic core package for reusable validation logic
([#&#8203;356](https://redirect.github.com/auth0/go-jwt-middleware/pull/356))
- Type-safe claims retrieval with generics (`GetClaims[T]()`,
`MustGetClaims[T]()`, `HasClaims()`)
- Structured logging support compatible with `log/slog`
- Support for 14 signature algorithms (HS256/384/512, RS256/384/512,
PS256/384/512, ES256/384/512, ES256K, EdDSA)
- Enhanced error responses with RFC 6750 compliance
- Trusted proxy configuration for DPoP behind reverse proxies
- Multiple issuer and audience support with new APIs
- Documentation and linting configuration
([#&#8203;361](https://redirect.github.com/auth0/go-jwt-middleware/pull/361))

##### Changed

- Migrated from square/go-jose to lestrrat-go/jwx v3
([#&#8203;358](https://redirect.github.com/auth0/go-jwt-middleware/pull/358))
- Module path updated to `github.com/auth0/go-jwt-middleware/v3`
([#&#8203;355](https://redirect.github.com/auth0/go-jwt-middleware/pull/355))
- Minimum Go version updated to 1.24
([#&#8203;355](https://redirect.github.com/auth0/go-jwt-middleware/pull/355))
- Update examples for v3 module path and new APIs

##### Breaking

- Pure options pattern: All constructors (`New()`) now require
functional options instead of positional parameters
- Context key: `ContextKey{}` is no longer exported - use
`GetClaims[T]()` helper function
- Custom claims now use generics for type safety
- `TokenExtractor` returns `ExtractedToken` (with scheme) instead of
`string`
- Type naming: `ExclusionUrlHandler` renamed to `ExclusionURLHandler`

##### Migration Example

**v2:**

```go
// Validator with positional parameters
jwtValidator, err := validator.New(
    keyFunc,
    validator.RS256,
    "https://issuer.example.com/",
    []string{"my-api"},
)

// Middleware
middleware := jwtmiddleware.New(jwtValidator.ValidateToken)

// Claims access via context key
claims := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
```

**v3:**

```go
// Validator with pure options
jwtValidator, err := validator.New(
    validator.WithKeyFunc(keyFunc),
    validator.WithAlgorithm(validator.RS256),
    validator.WithIssuer("https://issuer.example.com/"),
    validator.WithAudience("my-api"),
)

// Middleware with options
middleware, err := jwtmiddleware.New(
    jwtmiddleware.WithValidator(jwtValidator),
)

// Type-safe claims with generics
claims, err := jwtmiddleware.GetClaims[*validator.ValidatedClaims](r.Context())
```

See [MIGRATION\_GUIDE.md](MIGRATION_GUIDE.md) for complete migration
instructions.

***

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 10am on friday" in timezone
Europe/London, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/overmindtech/workspace).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41NS40IiwidXBkYXRlZEluVmVyIjoiNDMuNTYuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIiwiZ29sYW5nIl19-->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Upgrades a major authentication dependency and rewrites JWT
validation/claim extraction paths, so mistakes could break auth
enforcement or token parsing across the API server.
>
> **Overview**
> Migrates the codebase from `github.com/auth0/go-jwt-middleware/v2` to
`v3`, updating JWKS provider/validator/middleware construction to the
new options-based APIs and adjusting token extraction/claims retrieval.
>
> Because `v3` no longer exposes `ContextKey{}`, the auth middleware now
stores `*validator.ValidatedClaims` under a new
`auth.ValidatedClaimsContextKey{}` and updates downstream callers (e.g.
token expiry in `ManagementServiceHandler.CreateToken`) accordingly. The
API server init path now skips validator setup when
`AllowUnauthenticated` is enabled and tightens startup validation/error
logging for missing Auth0 config; related tests set
`AllowUnauthenticated: true` to accommodate `v3` rejecting empty
audience/domain values.
>
> Also updates `go.mod`/`go.sum` for new transitive deps pulled in by
`v3` (e.g. `lestrrat-go/jwx/v3`) and adds
`github.com/resend/resend-go/v3` to the main require block.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e8a54151abc72beb9973302047684ad983aa5b8e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: f8185846b5c05bebfd88c56b76c4d1bb95a592db
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Medium Risk**
> Primarily dependency hygiene, but it changes `go.mod` direct/indirect
requirements (and adds a few direct deps), which can affect module
resolution and builds.
>
> **Overview**
> Adds a new Bugbot rule requiring `go.mod` to contain exactly two
`require` blocks, with **all direct deps** in the first and **all `//
indirect` deps** in the second.
>
> Updates `go.mod` accordingly by moving several modules between
direct/indirect blocks and adding a few direct requirements (notably
`cloud.google.com/go/auth/oauth2adapt`,
`github.com/lithammer/fuzzysearch`, and
`sigs.k8s.io/structured-merge-diff/v6`).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f14e91b436c6b898c84f4570d0057733c5d8a304. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 3b467a861bd85514ce230e67ab433ffb49d4d600
<img width="2104" height="195" alt="image"
src="https://github.com/user-attachments/assets/6fa988f4-669c-41b7-a80e-97a936042b05"
/>

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Upgrade `github.com/google/go-github` from v80 to v84 and
`go-querystring` to resolve `ENG-3024`.

The upgrade involved updating import paths and `go.mod`/`go.sum`.
Although the upstream library had breaking changes between v80 and v84,
our specific usage of the GitHub client library was not affected, and
all existing tests pass.

---
Linear Issue:
[ENG-3024](https://linear.app/overmind/issue/ENG-3024/upgrade-github-libraries)

<p><a
href="https://cursor.com/agents/bc-a6d4c68b-e6b4-4705-8bad-124e129784ce"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-a6d4c68b-e6b4-4705-8bad-124e129784ce"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</p>

<!-- CURSOR_AGENT_PR_BODY_END -->

GitOrigin-RevId: e4fb546b7842a3fbf86f17a6fbf8a653f1a867b7
<img width="2934" height="1992" alt="image"
src="https://github.com/user-attachments/assets/b29c553f-4169-478a-8f1a-04f4b5caf8b1"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces a new Azure resource adapter and changes GET query parsing
for Azure to accept full resource IDs, which could affect query behavior
for Azure adapters if path-key mappings are missing or incorrect.
>
> **Overview**
> Adds a new Azure adapter for Elastic SAN volume snapshots, including
an Azure SDK client wrapper, lookups/linking to parent Elastic
SAN/volume group (and source volume when available), Terraform mappings,
and unit tests/mocks.
>
> Extends the core `standardAdapterCore.Get` path parsing to accept full
Azure resource IDs (e.g. from Terraform mappings) by extracting query
parts based on per-type path key definitions, returning explicit query
errors when unsupported.
>
> Also updates dependencies (`armelasticsan`), registers the new adapter
in `azure/manual/adapters.go`, adds ElasticSAN item types/resources +
resourceID path-key mapping, tightens k8s `Endpoints` staticcheck
suppressions, and makes multiple tests safer by returning after
`t.Fatal`/`t.Fatalf` to avoid nil dereferences.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c7e10ad43d6ec2031feae80015364980a25527af. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 97899d2e8b23eae053e5a6612affd3df5d872e73
<img width="1474" height="1002" alt="image"
src="https://github.com/user-attachments/assets/8b62255e-97f6-4f78-94d7-2a62b1553c48"
/>

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Medium Risk**
> Adds a new Azure discovery adapter and client wiring that will
increase API calls and linked-query generation for NSG scans. Risk is
moderate because it touches adapter initialization/registration and
paging logic, but is isolated to a new resource type.
>
> **Overview**
> Adds support for discovering Azure NSG *default* security rules by
introducing a new `NetworkDefaultSecurityRule` searchable adapter,
including `Get`, `Search`, and streaming search, and wiring it into the
Azure manual adapter registry.
>
> Introduces a thin `DefaultSecurityRulesClient` wrapper (with generated
GoMock) and updates resource-ID path extraction to understand
`azure-network-default-security-rule`; includes a dedicated unit test
suite covering paging, validation, and error cases.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
83470e2e2e56c5effbd6bbf5b777f1b1fc789bb9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 3fb0c7bdbef4b58a368deeffa2e726c9a2aaa793
…4217)

<img width="1476" height="1005" alt="image"
src="https://github.com/user-attachments/assets/1ae1b591-ae66-469a-afed-65435d760819"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Adds a new read-only adapter and resource-ID parsing entry without
changing existing adapter behavior; risk is mainly incorrect query-part
parsing or linking causing missing/extra discovered items.
>
> **Overview**
> Adds support for discovering Azure **Disk Access private endpoint
connections** as a first-class item type.
>
> Introduces a new compute client interface + generated mock, a
`SearchableWrapper` adapter that supports `Get` (by `diskAccessName` +
connection name) and `Search/SearchStream` (list connections for a disk
access), maps provisioning state to item health, and emits links to the
parent `ComputeDiskAccess` and referenced `NetworkPrivateEndpoint`.
>
> Wires the new adapter into Azure `Adapters()` (including metadata-only
mode) and updates `shared.GetResourceIDPathKeys` to parse this resource
type from Azure resource IDs; adds unit tests covering happy paths,
paging, nil-name filtering, and error handling.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fca452d9c38741050cb8789b70e89e5c94021b20. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: b5fa1330f689e5ff42d8a2a49e2621cad8986e62
## Summary

- Skip `fdatasync` on every bbolt commit in the sdpcache layer by
setting `NoSync: true` and `NoFreelistSync: true`
- Eliminates the single-writer bottleneck that cascaded into 36K stuck
goroutines under load (bbolt serializes write transactions behind
fdatasync, ~930 pool workers were queuing on the write lock)
- Safe because sdpcache is a pure cache -- crash durability provides no
value

## Linear Ticket

- **Ticket**:
[ENG-3089](https://linear.app/overmind/issue/ENG-3089/relax-bbolt-cache-durability-to-fix-goroutine-pileup-under-load)
— Relax bbolt cache durability to fix goroutine pileup under load
- **Purpose**: Remove the fdatasync bottleneck that causes cascading
goroutine pileups when sources are under heavy query load
- **Priority**: Urgent

## Changes

Single file change in `go/sdpcache/bolt_cache.go`:

1. Added a package-level `cacheOpenOptions` variable with `NoSync:
true`, `NoFreelistSync: true`, and the existing `Timeout: 5s`
2. Replaced all 7 inline `&bbolt.Options{Timeout: 5 * time.Second}` with
`cacheOpenOptions`

All sdpcache tests pass. No API or behavioral change -- only the fsync
guarantee is relaxed, which is irrelevant for a cache.

Made with [Cursor](https://cursor.com)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Disables bbolt fsync and freelist syncing for the cache DB, which can
improve throughput but increases the chance of cache corruption/loss
after crashes or power failures.
>
> **Overview**
> Introduces a shared `cacheOpenOptions` for all `bbolt.Open` calls in
`sdpcache` that sets `NoSync` and `NoFreelistSync` (keeping the existing
5s `Timeout`) to avoid per-commit `fdatasync` overhead.
>
> All cache DB open/reopen paths (startup, deletion recovery, and
compaction temp DB creation/reopen) are updated to use these relaxed
durability settings.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
60297db5a8b4e78b25a685c30ad8242f84ed17b1. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 4de3a30393489e86e14f68b16c8bbd81b8f1f3da
<img width="1475" height="1005" alt="image"
src="https://github.com/user-attachments/assets/bd0e6458-cbd6-4439-8e0d-0be453374457"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds a new Azure discovery adapter and wires it into adapter
initialization, introducing new Azure SDK calls and item-linking logic
that could affect discovery completeness/performance but not
security-critical flows.
>
> **Overview**
> Adds first-class discovery support for Azure Elastic SAN pools.
>
> Introduces a new `ElasticSanClient` wrapper around the Azure SDK plus
a `NewElasticSan` manual adapter that supports `List`, `ListStream`, and
`Get`, maps resources into SDP items (tags/health), and emits links to
related `ElasticSanVolumeGroup` (SEARCH) and `NetworkPrivateEndpoint`
(GET) resources. Wires the adapter into `manual/adapters.go` by
initializing `armelasticsan.NewElasticSansClient` and registering the
adapter (including the metadata-only nil-client path), and adds unit
tests + generated GoMock for the new client.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f25b3ccbcbd2327205c52c84d28c5416570fdfa5. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 9e98c7c8a21cbaaa192c5a6842fa8d1a1016ab68
## Implementation Notes — Deviations from Plan

Parts 1, 2b, and 3 were implemented exactly as specified. Part 2a (CLI
`terraform_plan.go`) has the following deviations, all driven by UX
feedback during implementation:

### CLI Output Formatting Changes

* **Change-level link:** Instead of a raw URL, now renders as a
clickable OSC 8 hyperlink with text "Open in Overmind ↗" (matching the
GitHub PR comment's "Open in Overmind ↗" wording)
* **Preamble text:** Changed from "Check the blast radius graph and
risks at:" to "View the blast radius graph and risks:"
* **Link indentation:** Removed — both per-risk and change-level links
have no indentation
* **Inter-risk spacing:** Added extra blank line between risk blocks for
readability
* **Description-to-link spacing:** "View risk ↗" immediately follows the
description (no blank line)

### Terminal Compatibility (not in original plan)

Added `supportsHyperlinks()` detection to gracefully handle terminals
that don't support OSC 8:

* **TTY check:** Skips hyperlinks when stdout is not a terminal (piped
output, file redirect)
* **CI check:** Skips hyperlinks when `$CI` is set
* **Env heuristics:** Checks `$TERM_PROGRAM`, `$VTE_VERSION`, `$TERM`
(kitty, 256color), and `$TMUX` to detect modern terminals
* **Fallback:** Renders raw URLs instead of hyperlinks in unsupported
environments
* `renderLink()` helper: Wraps the detection logic — returns OSC 8
hyperlink or raw URL

### Additional Tests (plan said no test changes needed)

* `TestBuildChangeMarkdownUTMAttribution` — asserts
`utm_source=github-comment` on change, signals, and risk URLs in
generated PR markdown
* `TestEnvSupportsHyperlinks` — 9 test cases covering CI, dumb terminal,
screen, tmux, TERM_PROGRAM, VTE_VERSION, kitty, 256color, and no-signal
scenarios
* `TestRenderLinkFallback` — verifies raw URL fallback when stdout is
not a TTY
* `TestRenderRiskPreview` — visual inspection test using real lipgloss
styles, ommited from CI test runs
* `TestTerminalHyperlink` — verifies OSC 8 escape sequence format

### Files Changed

* `services/api-server/service/changesservice.go` — UTM on 4 URLs (as
planned)
* `services/api-server/service/changesservice_test.go` — new UTM
assertion test
* `services/api-server/service/runtask.go` — UTM on 5 URLs (as planned)
* `cli/cmd/terraform_plan.go` — per-risk hyperlinks, terminal detection,
UTM params
* `cli/cmd/terraform_plan_test.go` — new test file with hyperlink,
detection, and visual preview tests
* `cli/cmd/changes_submit_plan.go` — UTM on change URL (as planned)

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to URL formatting/output (UTM query
params + clickable terminal links) and add tests; minimal impact beyond
link rendering and attribution tracking.
>
> **Overview**
> Adds UTM attribution parameters to Overmind links emitted by the CLI,
GitHub comment markdown, and HCP Terraform task outcomes (including
per-risk deep links), and includes the CLI version on `utm_source=cli`
links.
>
> Improves `terraform plan` CLI output by rendering OSC 8 clickable
hyperlinks when running in a compatible TTY, with environment-based
detection and new unit/preview tests to validate link rendering and UTM
presence.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
126f33b4e7412264727c493c2541b4246caf9b4a. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: d9143a2ac795069df3872be9b1c7b232644f58f0
<img width="1464" height="1025" alt="image"
src="https://github.com/user-attachments/assets/d9da04f8-c988-4ac2-b7bc-010af152bb34"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Mostly additive discovery logic, but it changes adapter initialization
and resource-ID parsing; `shared/utils.go` modifications appear to have
missing map-entry commas which could break builds and should be
verified.
>
> **Overview**
> Adds new Azure discovery adapters for **Elastic SAN** and **Elastic
SAN Volume Groups**, including SDK client wrappers/interfaces, adapter
registration in `manual/adapters.go`, and comprehensive unit tests plus
generated GoMock clients.
>
> The new Volume Group adapter supports `Get`/`Search`/streaming and
emits rich linked-item queries (Elastic SAN parent, volumes/snapshots
children, private endpoints, subnets, Key Vault, UAI, DNS) and health
mapping from provisioning state; `shared/utils.go` is extended to parse
volume-group resource IDs.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4b82060b80817f7cfe6ae129887851de76b61afd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: 13b4a371e9a7b1a4a5cb574dc420a5b2d2040d46
## Summary

- Relax `mapped_item_diffs` PK from `change_external_id` to `id` so
multiple rows can be stored per change, enabling accumulation of partial
Terraform plans from parallel CI workflows (e.g. Atlantis)
- Add `AddPlannedChanges` RPC that appends planned change items to an
existing change without triggering analysis
- Modify `StartChangeAnalysis` to load and combine pre-stored items with
any items provided in the request

## Linear Ticket

- **Ticket**:
[ENG-3098](https://linear.app/overmind/issue/ENG-3098/phase-1-backend-accumulate-partial-plans)
-- Phase 1: Backend -- Accumulate Partial Plans
- **Project**: [Multi-Plan Submission & GitHub App PR
Commenting](https://linear.app/overmind/project/multi-plan-submission-and-github-app-pr-commenting-7b3d32c2ff24)
- **Purpose**: Enable CI systems that produce multiple Terraform plans
in parallel to submit each plan independently and trigger a single
unified analysis
- **Approver**: TP Honey

## Changes

### Schema & Queries
- **Migration** (`20260311113037_relax_mapped_item_diffs_pk.sql`): Drops
PK on `change_external_id`, adds PK on `id`, adds btree index on
`change_external_id`
- **SQLC queries**: `GetMappedItemDiff` and
`GetMappedItemDiffsForAllModifications` changed from `:one` to `:many`;
new `SetPlannedChangesStored` query added
- All callers updated with `flattenMappedItemDiffs` helper across
`changesservice.go`, `changetimeline.go`, `labelservice.go`,
`signalservice.go`, `crm_sync.go`, and `area51/`

### Proto
- New `AddPlannedChanges` RPC +
`AddPlannedChangesRequest`/`AddPlannedChangesResponse` messages in
`ChangesService`

### Handlers
- `AddPlannedChanges`: validates auth, checks change is in `DEFINING`
status, appends items via `InsertMappedItemDiff`, sets
`planned_changes_stored` flag
- `StartChangeAnalysis`: always loads stored items and combines with any
provided items (supports single-plan, multi-plan, and hybrid flows)

### Tests
- `TestAddPlannedChanges`: 6 subtests covering append, accumulate, flag
verification, empty rejection, non-existent change
- `TestAddPlannedChangesConcurrent`: 10 goroutines inserting in
parallel, verifying no data loss
- `TestWriteMappedItemDiffs`: updated to verify multi-row insert
succeeds (was testing PK conflict)
- All existing test callers updated for `:many` return type

## Deviations from Approved Plan

### 1. `StartChangeAnalysis` combines stored + provided items (not
either/or)

**Plan**: When `changingItems` is empty, load from DB. When provided,
use them directly.
**Implementation**: Always loads stored items and appends to provided
items, supporting a hybrid flow.
**Why**: Review feedback from @DavidS-ovm -- the endpoint should work
when items are both stored AND provided, combining both rather than
picking one.

### 2. No error on empty `changingItems` with no stored items

**Plan**: Return `CodeFailedPrecondition` when both provided and stored
items are empty.
**Implementation**: Allow empty items through (analysis runs with empty
set as before).
**Why**: The existing
`TestStartChangeAnalysis/UpdatePlannedChanges_with_no_changes` test
calls `StartChangeAnalysis` with empty items and expects success (no-op
when River is unconfigured). Adding an error here broke backward
compatibility.

### 3. Additional callers updated beyond plan

**Plan**: Listed 4 callers to update (`changesservice.go` x3,
`changetimeline.go` x1).
**Implementation**: Also updated callers in `labelservice.go` (x2),
`signalservice.go` (x1), `crm_sync.go` (x1), `area51/changes.go` (x3),
`area51/llm.go` (x3) -- 10 additional call sites.
**Why**: The plan identified callers by grep but missed callers in other
packages. All callers of `GetMappedItemDiff` must be updated for the
`:one` to `:many` change.

### 4. `ErrNoRows` guards removed (not replaced)

**Plan**: Did not explicitly address `pgx.ErrNoRows` handling.
**Implementation**: Removed `ErrNoRows` guards at all
`GetMappedItemDiff` call sites.
**Why**: With `:many` queries, pgx returns an empty slice and `nil`
error instead of `ErrNoRows`
([pgx#2251](jackc/pgx#2251)). The guards were
dead code.

### 5. `flattenMappedItemDiffs` duplicated in `area51` package

**Plan**: Single helper in `changesservice.go`.
**Implementation**: Added a second copy in `area51/changes.go` because
`area51` is a separate package and cannot import from `service`.
**Why**: Go package boundaries -- the `area51` package has its own
callers of `GetMappedItemDiff`.

### 6. `schema.sql` manually edited (not generated by PG16)

**Plan**: Generate via `make_migration.sh` + `reset_database.sh`.
**Implementation**: Generated locally with PG18 (only version available
in cloud agent), then manually corrected to match PG16 `pg_dump` format.
**Why**: Cloud agent environment lacks Docker Compose services (PG16
runs as a sidecar in devcontainer). Installed PG18 from Debian packages
as a workaround. The schema.sql was corrected to match CI's PG16 output
by reverting to the parent commit's file and applying only the PK +
index changes.

### 7. No changes to `CalculateMappedResources` logic

**Plan**: Update comments in `CalculateMappedResources` to reflect new
semantics.
**Implementation**: Done as planned -- comment updated from "avoid
primary key conflicts" to "replace pre-stored partial plans with
resolved results". No functional changes.

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Enable accumulation of partial change plans by modifying the
`mapped_item_diffs` schema, adding an RPC to store items, and updating
`StartChangeAnalysis` to load them.

<div><a

href="https://cursor.com/agents/bc-18981e8f-e02d-44e6-a56c-45f12c629a3e"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"

src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-18981e8f-e02d-44e6-a56c-45f12c629a3e"><picture><source
media="(prefers-color-scheme: dark)"

srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"

src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

<!-- CURSOR_AGENT_PR_BODY_END -->

---------

GitOrigin-RevId: 19d327de5a8ada8cef3a4d7629821c2fc6a9fe27
…command (#4229)

## Summary

- Add `--no-start` flag to `submit-plan` so it stores planned changes
via `AddPlannedChanges` without triggering analysis, enabling multi-plan
workflows (e.g., Atlantis parallel planning)
- Add new `start-analysis` subcommand that triggers analysis on a change
with previously accumulated planned items, with optional `--wait` to
block until completion
- Extract shared analysis flags (`addAnalysisFlags`), config builder
(`buildAnalysisConfig`), and analysis polling (`waitForChangeAnalysis`)
into reusable helpers

## Linear Ticket

- **Ticket**:
[ENG-3104](https://linear.app/overmind/issue/ENG-3104/phase-2-cli-submit-plan-no-start-start-analysis)
-- Phase 2: CLI -- submit-plan --no-start + start-analysis
- **Purpose**: Enable CI systems that produce multiple Terraform plans
in parallel to submit each plan independently and then trigger a single
unified analysis
- **Project**: Multi-Plan Submission & GitHub App PR Commenting

## Changes

**Core feature** (reviewers should focus here):
- `cli/cmd/changes_submit_plan.go` -- branches on `--no-start`: calls
`AddPlannedChanges` RPC when set, `StartChangeAnalysis` when not.
Refactored to use shared `buildAnalysisConfig`.
- `cli/cmd/changes_start_analysis.go` -- new command: resolves change
via `--ticket-link`/`--uuid`/`--change`, builds analysis config, calls
`StartChangeAnalysis` with empty `changingItems` (backend loads
pre-stored items). Supports `--wait`.
- `cli/cmd/flags.go` -- new `addAnalysisFlags()`, `AnalysisConfig`
struct, `buildAnalysisConfig()`, and `waitForChangeAnalysis()` shared
helper.

**Refactoring:**
- `cli/cmd/changes_get_change.go`, `cli/cmd/changes_get_signals.go`,
`cli/cmd/changes_start_change.go` -- replaced inline analysis-polling
loops with shared `waitForChangeAnalysis()`.

**Tests:**
- `cli/cmd/changes_start_analysis_test.go` -- tests for
`addAnalysisFlags` registration, `buildAnalysisConfig` with various flag
combinations, `startAnalysisCmd` flag presence, and `submitPlanCmd`
`--no-start` flag registration.

**Unrelated:**
- `.cursor/commands/create-project-plan.md` -- adds a model gate
requiring MAX mode (separate from Phase 2 work).

## Deviations from Approved Plan

- **`waitForChangeAnalysis` shared helper (addition)**: The plan did not
mention extracting the analysis polling loop. The implementation
extracted the duplicated poll-until-done loop from `get-change`,
`get-signals`, and `start-change` into a shared
`waitForChangeAnalysis()` in `flags.go`. This reduces ~120 lines of
duplication across 3 files and is needed by `start-analysis --wait`.
- **UTM tracking on change URLs**: `submit-plan` and `start-analysis`
append `?utm_source=cli&cli_version=<version>` to their change URL
output. This follows the established pattern from ENG-3047 Phase 1 (UTM
Attribution on External Links) and was applied to the new code paths
rather than leaving them inconsistent with the rest of the CLI.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: David Schmitt <DavidS-ovm@users.noreply.github.com>
GitOrigin-RevId: 1165783ff4d163d05efd654abe5cd6245657fe53
Introduce WithResourceMetadata middleware that sets a WWW-Authenticate
header with a resource_metadata URL on 401 responses, enabling MCP
clients (e.g. Cursor) to discover the authorization server
automatically. Serve PRM at both path-based and root well-known URIs per
RFC 9728.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches authentication-adjacent HTTP middleware and changes
routing/headers for `401` responses, which could affect client auth
flows and caching/proxies if misconfigured. Scope is limited to MCP
endpoints and well-known PRM routes with added tests.
>
> **Overview**
> Enables MCP OAuth discovery per RFC 9728 by adding
`auth.WithResourceMetadata`, which injects `WWW-Authenticate: Bearer
resource_metadata="…"` on `401 Unauthorized` responses.
>
> Wires this into the api-server’s `/area51/mcp` route (when
`MCPResourceMetadataURL` is configured) and serves Protected Resource
Metadata from both `/.well-known/oauth-protected-resource/area51/mcp`
(path-based) and the root `/.well-known/oauth-protected-resource`
endpoint; adds unit tests covering the new header behavior.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2f65fb73d3efae8da6f553ff42614f481f2ca406. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
GitOrigin-RevId: c8cc2f110b6f9e83a8663ffd4ee93ddc74ce63a0
## Summary

- Implements Phase 3 of the Multi-Plan Submission + GitHub App PR
Commenting project: the backend posts change analysis results as a
comment on the linked GitHub PR using the installed GitHub App
- Uses a static hidden HTML marker (`<!-- overmind-change-summary -->`)
for idempotent upserts — re-runs update the same comment instead of
duplicating
- All PR comment failures are non-fatal — logged and traced but never
fail the analysis job

## Linear Ticket

- **Ticket**: [ENG-3113](https://linear.app/overmind/issue/ENG-3113) —
Phase 3: Backend — GitHub App PR Commenting
- **Purpose**: Enable the backend to post analysis results directly to
GitHub PRs, so CI/CLI can detect `github_app_active` and skip its own
commenting
- **Depends on**: Phase 1 (ENG-3098, merged) and Phase 2 (ENG-3104, PR
#4229 rebased onto this branch)

## Changes

### Proto (`sdp/changes.proto`)
- `StartChangeAnalysisRequest`: added `post_github_comment` (field 8)
- `StartChangeAnalysisResponse`: added `github_app_active` (field 1)
- `GetChangeSummaryResponse`: added `github_app_comment_posted` (field
2)

### Schema + SQLC
- Migration adding nullable `github_comment_id bigint` to `changes`
table
- New `SetGithubCommentID` query

### GitHub App PR Commenting (`githubapp/githubapp.go`)
- `ParseGitHubPRURL` — extracts owner/repo/number from PR URLs
- `PostOrUpdatePRComment` — posts or updates a comment using static
hidden HTML marker for dedup; handles 403 gracefully (returns 0, nil)
- `CheckInstallationCanComment` — verifies `pull_requests:write`
permission on the installation
- `findExistingComment` documented with marker-vs-DB tradeoff rationale

### Shared Markdown & Signal Aggregation (`service/change_summary.go`)
- `BuildChangeSummaryMarkdown` — gathers data from DB and renders the
same markdown template used by `get-change --format markdown`. Takes
`*config.LLMConfig` and performs LLM-based signal aggregation
internally.
- `AggregateChangeOverviewSignals` — shared signal aggregation function
extracted from `signalservice.go`, called by both
`GetChangeOverviewSignals` RPC and `BuildChangeSummaryMarkdown`. Ensures
PR comments show identical signal values to the dashboard/CLI.
- `computeBlastRadiusContext` — extracted from `GetChangeSummary` to
remove duplication

### Signal Service Refactor (`service/signalservice.go`)
- `GetChangeOverviewSignals` RPC handler now delegates to shared
`AggregateChangeOverviewSignals`, keeping only auth checks and RPC
wrapping (~90 lines of business logic removed)

### Worker Wiring (`changeanalysis/change_analysis.go`)
- `PostGithubComment bool` added to `ChangeAnalysisCalculationArgs`
- `FrontendDNS` and `BuildMarkdownFunc` callback added to worker struct
(callback avoids circular import)
- `postAnalysisGithubPRComment` runs after `STATUS_DONE` — non-fatal,
logged + Sentry, with `cenkalti/backoff/v5` retries
- `ovm.auth.accountName` set on the comment span for worker-context
observability
- OTEL attributes use `ovm.github.pr_comment.skipped_reason` for all
early-exit paths

### RPC Wiring (`changesservice.go`)
- `checkGithubAppCanComment` helper checks installation permissions
- `StartChangeAnalysis` returns `github_app_active` and passes flag to
worker args
- `GetChangeSummary` returns `github_app_comment_posted` from
`change.GithubCommentID.Valid`

### LLM: Fix web search `outputToParam` crash
- **Root cause**: `outputToParam` crashed with `"no action present"`
when OpenAI returned a `web_search_call` whose action type was
unrecognized by the SDK's `AsAny()` (returns `nil` for types other than
`search`, `open_page`, `find_in_page`). This happened
non-deterministically, causing test flakes in `TestWebSearchTool`.
- **Fix**: The `default` case now sets `skipped = true` instead of
returning a fatal error, since web search items are purely informational
and already ignored in the main processing loop. The caller emits a
structured `Warn` log with `callId`, `status`, `actionType`,
`actionRawJSON`, and `model` for Honeycomb analysis. The span event also
now includes `actionTypeRaw` and `actionRawJSON`.
- **Secondary fix**: The `search` action case was silently dropping
`Queries` and `Sources` fields during the output-to-param conversion —
these are now preserved.

### Tests
- `githubapp_test.go`: URL parsing (9 cases), comment create/update/403,
permissions check (httptest mocks), pinned marker format test to prevent
silent duplicate-comment regressions
- `changesservice_test.go`: `checkGithubAppCanComment`,
`SetGithubCommentID` DB roundtrip
- `change_summary_test.go`: `BuildChangeSummaryMarkdown`,
`AggregateChangeOverviewSignals` (single-signal-per-category
pass-through), `computeBlastRadiusContext`

## Deviations from Approved Plan

- **BuildMarkdownFunc callback instead of direct import**: The plan
suggested the worker call `BuildChangeSummaryMarkdown` directly. Due to
a circular import (`changeanalysis` cannot import `service`), a
`BuildMarkdownFunc` callback field was added to the worker struct, set
at init time in `main.go`. Same behavior, different wiring.

- **Retry with `cenkalti/backoff/v5`**: The plan did not specify retry
logic for `PostOrUpdatePRComment`. Exponential backoff retries (3
attempts) were added in `postAnalysisGithubPRComment` to handle
transient GitHub API failures, since the library was already available
in the project.

- **All PR comment code consolidated in `githubapp.go`**: The plan
suggested a separate `pr_comment.go` file (and
`changeanalysis/github_comment.go`). All PR comment functions
(`ParseGitHubPRURL`, `PostOrUpdatePRComment`,
`CheckInstallationCanComment`, `findExistingComment`) and their tests
were consolidated into the existing `githubapp.go` / `githubapp_test.go`
to combat file sprawl and keep all GitHub App logic grouped together.
`postAnalysisGithubPRComment` remains in `change_analysis.go` alongside
the existing worker methods.

- **No `postAnalysisGithubPRComment` unit tests in
`change_analysis_test.go`**: The plan called for direct tests of this
method. Testing it requires mocking the full River worker context,
GitHub API, and DB. The function is non-fatal by design and its
components (`PostOrUpdatePRComment`, `BuildChangeSummaryMarkdown`,
`AggregateChangeOverviewSignals`) are tested individually.

---------

Co-authored-by: David Schmitt <david.schmitt@overmind.tech>
GitOrigin-RevId: ed78774f122809a4568368f44c21b677ebcb2107
## Summary

- Add `--comment` flag to CLI (`submit-plan`, `start-analysis`) and
`--wait` flag to `get-change` to enable GitHub App PR commenting and
control analysis polling
- Add `comment` and `wait` inputs to the `submit-plan` composite action,
with backward-compatible `fetch-change` deprecation
- Migrate internal workflows (`terraform.yml`, `terraform-meta.yml`) to
use the new inputs and disable legacy Slack plan notifications

## Linear Ticket

- **Ticket**:
[ENG-3123](https://linear.app/overmind/issue/ENG-3123/phase-4-cli-action-wire-comment-flag-auto-detect-and-skip-wait)
— Phase 4: CLI + Action — Wire Comment Flag, Auto-Detect, and Skip Wait
- **Project**: Multi-Plan Submission & GitHub App PR Commenting (Phase 4
of 5)

## Changes

### CLI (`cli/cmd/`)
- `flags.go`: New `--comment` bool flag on `addAnalysisFlags`,
requesting the GitHub App to post PR comments
- `changes_submit_plan.go`: When `--comment` is set, outputs eval-able
`CHANGE_URL` and `GITHUB_APP_ACTIVE` assignments instead of bare URL;
passes `PostGithubComment` to `StartChangeAnalysis` RPC
- `changes_start_analysis.go`: Same `--comment` behavior and
`PostGithubComment` plumbing for the standalone `start-analysis` command
- `changes_get_change.go`: Adds `--wait` flag (default `true`); skips
`waitForChangeAnalysis` when `--wait=false`. Also fixes `MarkDeprecated`
referencing wrong command (`submitPlanCmd` → `getChangeCmd`)
- Housekeeping: replace `_ = MarkDeprecated`/`MarkHidden` with
`cobra.CheckErr(...)` across 7 call sites

### Action (`actions/submit-plan/action.yml`)
- New `comment` (default `"true"`) and `wait` (default `"false"`) inputs
- `fetch-change` marked deprecated with backward-compatible shim
- New `github-app-active` output; fixes `message` output (was
incorrectly mapped to `change-url`)
- When `comment=true`: tries `--comment` flag, falls back gracefully if
CLI is older (`unknown flag` detection), and only fetches/posts sticky
comment when the GitHub App is not active
- Stderr isolation: redirects stderr to temp files (`submit-stderr.log`,
`get-stderr.log`) instead of `2>&1` to prevent logrus output from
polluting eval'd shell assignments or PR comment content

### Workflows (`.github/workflows/`)
- `terraform.yml` and `terraform-meta.yml`: migrate from `fetch-change`
to `comment`, add push-event guard, disable Slack plan notifications
(GitHub App replaces them)

## Deviations from Approved Plan

### Additions not in the plan

1. **Stderr isolation in action shell logic**
(`actions/submit-plan/action.yml`): The plan uses `eval "$(cli ...)"`
directly. The implementation captures stdout to a variable with stderr
redirected to temp files (`2>./overmindtech/submit-stderr.log`,
`2>./overmindtech/get-stderr.log`), then evals the variable. This
prevents logrus stderr lines (containing invalid bash identifiers like
`change-url`) from breaking `eval`, and prevents log noise from leaking
into PR comment content.

2. **Backward compatibility fallback for older CLIs**
(`actions/submit-plan/action.yml`): The plan assumes the CLI supports
`--comment` and `--wait`. The implementation adds fallback: if the CLI
returns "unknown flag" (detected via the stderr temp file), it falls
back to the legacy code path and logs a `::notice::`. This enables
rolling out the action change before all CLI versions support the new
flags.

3. **Push-event guard in workflows** (`.github/workflows/terraform.yml`,
`terraform-meta.yml`): The plan removes `fetch-change` without adding an
equivalent guard. The implementation passes `comment: ${{
github.event.number != '' }}` instead of unconditional `comment: true`,
preventing comment logic from running on push events where there's no PR
number.

### Minor approach changes

4. **Sticky comment condition**: The plan checks `inputs.comment !=
'false'`. The implementation checks `steps.submit-plan.outputs.message
!= ''` — more robust since `message` is only populated when the change
was actually fetched.

5. **`fetch-change` deprecation mapping**: The plan maps both `true` →
`comment: true` and `false` → `comment: false`. The implementation only
remaps the `false` case (setting `OVM_COMMENT='false'`), since `comment`
already defaults to `"true"`.

### Omissions from the plan

6. **Part 8 — Linear issue for Slack notification feature**: The plan
calls for creating a Linear issue titled "Investigate Slack notification
feature for change analysis results". This was **not created** and
should be filed separately.

## Test Plan

- [x] Flag registration tests for `--comment` on `submit-plan` and
`start-analysis`
- [x] Flag registration test for `--wait` on `get-change` (default
`true`)
- [ ] Verify `submit-plan` action with `comment: true` on a PR event
(GitHub App active path)
- [ ] Verify `submit-plan` action with `comment: true` when GitHub App
is not installed (sticky comment fallback)
- [ ] Verify `submit-plan` action with older CLI that doesn't support
`--comment` (graceful fallback)
- [ ] Verify `comment: false` skips all PR commenting logic
- [ ] Verify `wait: true` blocks until analysis completes and populates
`message` output

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Medium Risk**
> Changes GitHub Actions and CLI behavior around when to wait for
Overmind analysis and where results are posted; misconfiguration could
lead to missing plan feedback or altered CI timing. Touches deployment
workflows but not Terraform execution logic itself.
>
> **Overview**
> Routes Terraform plan reporting away from Slack and toward PR
comments, wiring workflows to pass a new `comment` input to
`actions/submit-plan` (and disabling the plan-to-Slack steps).
>
> Updates the `submit-plan` composite action and Overmind CLI to support
`comment`/`wait` controls (deprecating `fetch-change`), including
conditional fetching of analysis results, GitHub App vs sticky-comment
handling, and new CLI flags/outputs (`--comment`, `get-change --wait`).
Also tightens flag handling by checking errors when hiding/deprecating
Cobra flags and adds targeted tests for the new flags.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1690417e01470b73fe9bd371b2eb1f878895d32d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

GitOrigin-RevId: a0fb20e395e0f0ffb62e32a3970c48dff980e184
@tphoney tphoney merged commit 002e7aa into main Mar 12, 2026
@tphoney tphoney deleted the copybara/v1.17.0 branch March 12, 2026 15:55
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.

6 participants