Skip to content

Commit 7ec62f2

Browse files
DavidS-ovmtphoney
authored andcommitted
Eng 3123 cli action comment wait (#4245)
## 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
1 parent 55b2fd3 commit 7ec62f2

10 files changed

Lines changed: 67 additions & 13 deletions

cmd/changes_get_change.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ func GetChange(cmd *cobra.Command, args []string) error {
7171
}
7272

7373
client := AuthenticatedChangesClient(ctx, oi)
74-
if err := waitForChangeAnalysis(ctx, client, changeUuid, lf); err != nil {
75-
return err
74+
if viper.GetBool("wait") {
75+
if err := waitForChangeAnalysis(ctx, client, changeUuid, lf); err != nil {
76+
return err
77+
}
7678
}
7779

7880
app, _ = strings.CutSuffix(app, "/")
@@ -142,7 +144,8 @@ func init() {
142144
getChangeCmd.PersistentFlags().String("status", "CHANGE_STATUS_DEFINING", "The expected status of the change. Use this with --ticket-link to get the first change with that status for a given ticket link. Allowed values: CHANGE_STATUS_DEFINING (ready for analysis/analysis in progress), CHANGE_STATUS_HAPPENING (deployment in progress), CHANGE_STATUS_DONE (deployment completed)")
143145

144146
getChangeCmd.PersistentFlags().String("frontend", "", "The frontend base URL")
145-
_ = submitPlanCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead.") // MarkDeprecated only errors if the flag doesn't exist, we fall back to using app
147+
cobra.CheckErr(getChangeCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead."))
148+
getChangeCmd.PersistentFlags().Bool("wait", true, "Wait for analysis to complete before returning. Set to false to return immediately with the current status.")
146149
getChangeCmd.PersistentFlags().String("format", "json", "How to render the change. Possible values: json, markdown")
147150
getChangeCmd.PersistentFlags().StringSlice("risk-levels", []string{"high", "medium", "low"}, "Only show changes with the specified risk levels. Allowed values: high, medium, low")
148151
}

cmd/changes_get_change_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ import (
77
"github.com/overmindtech/cli/go/sdp-go"
88
)
99

10+
func TestGetChangeCmdHasWaitFlag(t *testing.T) {
11+
t.Parallel()
12+
13+
flag := getChangeCmd.PersistentFlags().Lookup("wait")
14+
if flag == nil {
15+
t.Error("Expected wait flag to be registered on get-change command")
16+
return
17+
}
18+
19+
if flag.DefValue != "true" {
20+
t.Errorf("Expected wait flag default value to be 'true', got %q", flag.DefValue)
21+
}
22+
}
23+
1024
func TestValidateChangeStatus(t *testing.T) {
1125
tests := []struct {
1226
name string

cmd/changes_get_signals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ func init() {
9898
getSignalsCmd.PersistentFlags().String("status", "CHANGE_STATUS_DEFINING", "The expected status of the change. Use this with --ticket-link to get the first change with that status for a given ticket link. Allowed values: CHANGE_STATUS_DEFINING (ready for analysis/analysis in progress), CHANGE_STATUS_HAPPENING (deployment in progress), CHANGE_STATUS_DONE (deployment completed)")
9999

100100
getSignalsCmd.PersistentFlags().String("frontend", "", "The frontend base URL")
101-
_ = getSignalsCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead.") // MarkDeprecated only errors if the flag doesn't exist, we fall back to using app
101+
cobra.CheckErr(getSignalsCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead."))
102102
getSignalsCmd.PersistentFlags().String("format", "json", "How to render the signals. Possible values: json, markdown")
103103
}

cmd/changes_start_analysis.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,15 @@ func StartAnalysis(cmd *cobra.Command, args []string) error {
5757

5858
client := AuthenticatedChangesClient(ctx, oi)
5959

60-
_, err = client.StartChangeAnalysis(ctx, &connect.Request[sdp.StartChangeAnalysisRequest]{
60+
resp, err := client.StartChangeAnalysis(ctx, &connect.Request[sdp.StartChangeAnalysisRequest]{
6161
Msg: &sdp.StartChangeAnalysisRequest{
6262
ChangeUUID: changeUUID[:],
6363
ChangingItems: nil, // uses pre-stored items from AddPlannedChanges
6464
BlastRadiusConfigOverride: analysisConfig.BlastRadiusConfig,
6565
RoutineChangesConfigOverride: analysisConfig.RoutineChangesConfig,
6666
GithubOrganisationProfileOverride: analysisConfig.GithubOrgProfile,
6767
Knowledge: analysisConfig.KnowledgeFiles,
68+
PostGithubComment: viper.GetBool("comment"),
6869
},
6970
})
7071
if err != nil {
@@ -78,7 +79,13 @@ func StartAnalysis(cmd *cobra.Command, args []string) error {
7879
app, _ = strings.CutSuffix(app, "/")
7980
changeUrl := fmt.Sprintf("%v/changes/%v?utm_source=cli&cli_version=%v", app, changeUUID, tracing.Version())
8081
log.WithContext(ctx).WithFields(lf).WithField("change-url", changeUrl).Info("Change analysis started")
81-
fmt.Println(changeUrl)
82+
83+
if viper.GetBool("comment") {
84+
fmt.Printf("CHANGE_URL='%s'\n", changeUrl)
85+
fmt.Printf("GITHUB_APP_ACTIVE='%v'\n", resp.Msg.GetGithubAppActive())
86+
} else {
87+
fmt.Println(changeUrl)
88+
}
8289

8390
if viper.GetBool("wait") {
8491
log.WithContext(ctx).WithFields(lf).Info("Waiting for analysis to complete")

cmd/changes_start_analysis_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func TestAddAnalysisFlags(t *testing.T) {
2727
{"blast-radius-max-time", "blast-radius-max-time", "duration"},
2828
{"change-analysis-target-duration", "change-analysis-target-duration", "duration"},
2929
{"signal-config", "signal-config", "string"},
30+
{"comment", "comment", "bool"},
3031
}
3132

3233
for _, tt := range tests {
@@ -189,6 +190,7 @@ func TestStartAnalysisCmdFlags(t *testing.T) {
189190
{"blast-radius-link-depth", "blast-radius-link-depth"},
190191
{"blast-radius-max-items", "blast-radius-max-items"},
191192
{"signal-config", "signal-config"},
193+
{"comment flag", "comment"},
192194
}
193195

194196
for _, tt := range tests {
@@ -205,6 +207,20 @@ func TestStartAnalysisCmdFlags(t *testing.T) {
205207
}
206208
}
207209

210+
func TestSubmitPlanCmdHasCommentFlag(t *testing.T) {
211+
t.Parallel()
212+
213+
flag := submitPlanCmd.PersistentFlags().Lookup("comment")
214+
if flag == nil {
215+
t.Error("Expected comment flag to be registered on submit-plan command")
216+
return
217+
}
218+
219+
if flag.DefValue != "false" {
220+
t.Errorf("Expected comment flag default value to be 'false', got %q", flag.DefValue)
221+
}
222+
}
223+
208224
func TestSubmitPlanCmdHasNoStartFlag(t *testing.T) {
209225
t.Parallel()
210226

cmd/changes_submit_plan.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,12 @@ func SubmitPlan(cmd *cobra.Command, args []string) error {
258258
log.WithContext(ctx).WithFields(lf).Info("Re-using change")
259259
}
260260

261+
var githubAppActive bool
262+
261263
if viper.GetBool("no-start") {
264+
if viper.GetBool("comment") {
265+
log.WithContext(ctx).WithFields(lf).Info("--comment has no effect with --no-start; pass --comment to start-analysis instead")
266+
}
262267
// Store planned changes without starting analysis (multi-plan workflow)
263268
_, err = client.AddPlannedChanges(ctx, &connect.Request[sdp.AddPlannedChangesRequest]{
264269
Msg: &sdp.AddPlannedChangesRequest{
@@ -281,14 +286,15 @@ func SubmitPlan(cmd *cobra.Command, args []string) error {
281286
return err
282287
}
283288

284-
_, err = client.StartChangeAnalysis(ctx, &connect.Request[sdp.StartChangeAnalysisRequest]{
289+
resp, err := client.StartChangeAnalysis(ctx, &connect.Request[sdp.StartChangeAnalysisRequest]{
285290
Msg: &sdp.StartChangeAnalysisRequest{
286291
ChangeUUID: changeUUID[:],
287292
ChangingItems: plannedChanges,
288293
BlastRadiusConfigOverride: analysisConfig.BlastRadiusConfig,
289294
RoutineChangesConfigOverride: analysisConfig.RoutineChangesConfig,
290295
GithubOrganisationProfileOverride: analysisConfig.GithubOrgProfile,
291296
Knowledge: analysisConfig.KnowledgeFiles,
297+
PostGithubComment: viper.GetBool("comment"),
292298
},
293299
})
294300
if err != nil {
@@ -298,12 +304,19 @@ func SubmitPlan(cmd *cobra.Command, args []string) error {
298304
message: "Failed to start change analysis",
299305
}
300306
}
307+
githubAppActive = resp.Msg.GetGithubAppActive()
301308
}
302309

303310
app, _ = strings.CutSuffix(app, "/")
304311
changeUrl := fmt.Sprintf("%v/changes/%v?utm_source=cli&cli_version=%v", app, changeUUID, tracing.Version())
305312
log.WithContext(ctx).WithFields(lf).WithField("change-url", changeUrl).Info("Change ready")
306-
fmt.Println(changeUrl)
313+
314+
if viper.GetBool("comment") {
315+
fmt.Printf("CHANGE_URL='%s'\n", changeUrl)
316+
fmt.Printf("GITHUB_APP_ACTIVE='%v'\n", githubAppActive)
317+
} else {
318+
fmt.Println(changeUrl)
319+
}
307320

308321
return nil
309322
}
@@ -385,7 +398,7 @@ func init() {
385398
addAnalysisFlags(submitPlanCmd)
386399

387400
submitPlanCmd.PersistentFlags().String("frontend", "", "The frontend base URL")
388-
_ = submitPlanCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead.") // MarkDeprecated only errors if the flag doesn't exist, we fall back to using app
401+
cobra.CheckErr(submitPlanCmd.PersistentFlags().MarkDeprecated("frontend", "This flag is no longer used and will be removed in a future release. Use the '--app' flag instead."))
389402

390403
submitPlanCmd.PersistentFlags().String("auto-tag-rules", "", "The path to the auto-tag rules file. If not provided, it will check the default location which is '.overmind/auto-tag-rules.yaml'. If no rules are found locally, the rules configured through the UI are used.")
391404

cmd/explore.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ func init() {
666666

667667
// hidden flag to enable Azure preview support
668668
exploreCmd.PersistentFlags().Bool("enable-azure-preview", false, "Enable Azure source support (preview feature).")
669-
exploreCmd.PersistentFlags().MarkHidden("enable-azure-preview") //nolint:errcheck // not possible to error
669+
cobra.CheckErr(exploreCmd.PersistentFlags().MarkHidden("enable-azure-preview"))
670670
}
671671

672672
// unifiedGCPConfigs collates the given GCP configs by project ID.

cmd/flags.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ func addAnalysisFlags(cmd *cobra.Command) {
130130
cmd.PersistentFlags().Int32("blast-radius-link-depth", 0, "Used in combination with '--blast-radius-max-items' to customise how many levels are traversed when calculating the blast radius. Larger numbers will result in a more comprehensive blast radius, but may take longer to calculate. Defaults to the account level settings.")
131131
cmd.PersistentFlags().Int32("blast-radius-max-items", 0, "Used in combination with '--blast-radius-link-depth' to customise how many items are included in the blast radius. Larger numbers will result in a more comprehensive blast radius, but may take longer to calculate. Defaults to the account level settings.")
132132
cmd.PersistentFlags().Duration("blast-radius-max-time", 0, "Maximum time duration for blast radius calculation (e.g., '5m', '15m', '30m'). When the time limit is reached, the analysis continues with risks identified up to that point. Defaults to the account level settings (QUICK: 10m, DETAILED: 15m, FULL: 30m). Valid range: 1m to 30m.")
133-
_ = cmd.PersistentFlags().MarkDeprecated("blast-radius-max-time", "This flag is no longer used and will be removed in a future release. Use the '--change-analysis-target-duration' flag instead.")
133+
cobra.CheckErr(cmd.PersistentFlags().MarkDeprecated("blast-radius-max-time", "This flag is no longer used and will be removed in a future release. Use the '--change-analysis-target-duration' flag instead."))
134134
cmd.PersistentFlags().Duration("change-analysis-target-duration", 0, "Target duration for change analysis planning (e.g., '5m', '15m', '30m'). This is NOT a hard deadline - the blast radius phase uses 67% of this target to stop gracefully. The job can run slightly past this target and is only hard-stopped at 30 minutes. Defaults to the account level settings (QUICK: 10m, DETAILED: 15m, FULL: 30m). Valid range: 1m to 30m.")
135135
cmd.PersistentFlags().String("signal-config", "", "The path to the signal config file. If not provided, it will check the default location which is '.overmind/signal-config.yaml'. If no config is found locally, the config configured through the UI is used.")
136+
cmd.PersistentFlags().Bool("comment", false, "Request the GitHub App to post analysis results as a PR comment. Requires the account to have the Overmind GitHub App installed with pull_requests:write.")
136137
}
137138

138139
// AnalysisConfig holds all the configuration needed to start change analysis.

cmd/knowledge_list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,5 @@ func init() {
107107
knowledgeCmd.AddCommand(knowledgeListCmd)
108108

109109
knowledgeListCmd.Flags().String("dir", ".", "Directory to start searching from")
110-
knowledgeListCmd.Flags().MarkHidden("dir") //nolint:errcheck // not possible to error
110+
cobra.CheckErr(knowledgeListCmd.Flags().MarkHidden("dir"))
111111
}

cmd/terraform.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func init() {
3535

3636
// hidden flag to enable Azure preview support
3737
terraformCmd.PersistentFlags().Bool("enable-azure-preview", false, "Enable Azure source support (preview feature).")
38-
terraformCmd.PersistentFlags().MarkHidden("enable-azure-preview") //nolint:errcheck // not possible to error
38+
cobra.CheckErr(terraformCmd.PersistentFlags().MarkHidden("enable-azure-preview"))
3939
}
4040

4141
var applyOnlyArgs = []string{

0 commit comments

Comments
 (0)