Repository navigation
feat(workflow): add --watch to workflow approve for multi-gate runs (#26) - #63
Merged
Merged
Conversation
) Stays attached to a run and decides each newly-appearing environment approval gate (after confirmation, unless --yes) until the run completes, instead of requiring a re-run of `workflow approve` after every gate. - New --watch/-W, --interval (default 10s, min 5), --timeout (0 = none) flags - --watch --yes requires --environment, so an unattended watch never approves a gate that didn't exist when it started - Ctrl-C stops cleanly without deciding anything further - --watch --dry-run falls through to the existing non-looping preview - NDJSON event output under --json (one compact object per event) - internal/testutils: MockGitHubServer.SetResponseSequence lets tests simulate a run's state changing across successive polls Closes #26 Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD
pradyb
changed the base branch from
feat/1-keyring-token-storage
to
develop
September 30, 2026 03:04
…e-watch Retargeting #63 from feat/1-keyring-token-storage to develop after #62 (#1) merged; both branches added an Unreleased/Added entry, kept both (#26's entry first, matching this branch's existing order). Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD
pradyb
added a commit
that referenced
this pull request
Sep 30, 2026
The merge of develop (containing #63's squash-merged commit) into this branch (which already had #63's pre-squash commits as ancestors) silently duplicated the entire "workflow approve --watch" test section instead of recognizing it as already present, since the two copies reached the file via different diff paths (squash vs. original commits) and didn't line up for git's merge to dedupe automatically. go vet caught it immediately ("approveRunPath redeclared in this block"). Removed the duplicate, confirmed the file is otherwise identical, gofmt clean, full suite green. Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD
pradyb
added a commit
that referenced
this pull request
Sep 30, 2026
) * feat(config): store per-owner tokens in the OS keyring instead of plaintext (#1) Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD * feat(workflow): add --watch to workflow approve for multi-gate runs (#26) Stays attached to a run and decides each newly-appearing environment approval gate (after confirmation, unless --yes) until the run completes, instead of requiring a re-run of `workflow approve` after every gate. - New --watch/-W, --interval (default 10s, min 5), --timeout (0 = none) flags - --watch --yes requires --environment, so an unattended watch never approves a gate that didn't exist when it started - Ctrl-C stops cleanly without deciding anything further - --watch --dry-run falls through to the existing non-looping preview - NDJSON event output under --json (one compact object per event) - internal/testutils: MockGitHubServer.SetResponseSequence lets tests simulate a run's state changing across successive polls Closes #26 Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD * feat(workflow): support NDJSON output for view --watch --json (#64) --watch and --json on `workflow view` were previously mutually exclusive (--watch is an interactive bubbletea view). With --json, --watch now instead streams each job/step status transition as compact single-line JSON (NDJSON) until the run completes, for scripting or logging a watched run: job_started, job_completed, step_started, step_completed, run_done - pkg/workflow/watch_view.go: WatchViewRun polls GetWorkflowRunDetail, diffing job/step status against what was already reported so a poll with no change produces no events; a job/step that jumps straight to completed (skipping in_progress) reports only completed, not a synthetic started - Reuses the existing --interval flag and the DI Sleep pattern from #26's approve --watch; stops cleanly on ctx cancellation (Ctrl-C) - The plain interactive --watch (no --json) is unchanged Closes #64 Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD * fix: remove duplicate approve --watch test block from the develop merge The merge of develop (containing #63's squash-merged commit) into this branch (which already had #63's pre-squash commits as ancestors) silently duplicated the entire "workflow approve --watch" test section instead of recognizing it as already present, since the two copies reached the file via different diff paths (squash vs. original commits) and didn't line up for git's merge to dedupe automatically. go vet caught it immediately ("approveRunPath redeclared in this block"). Removed the duplicate, confirmed the file is otherwise identical, gofmt clean, full suite green. Claude-Session: https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What / why
workflow approvepreviously handled one round of gates per invocation — a run with severalsequential environment gates needed the command re-run manually after each approval. This adds
--watch, which stays attached to the run and decides each newly-appearing gate (afterconfirmation, unless
--yes) until the run completes.--watch/-W: enable the loop (requires exactly one-r)--interval <sec>: polling interval, default 10, minimum 5 (enforced at runtime)--timeout <duration>: give up after this long (e.g.30m); 0 (default) = no timeout--watch --yesadditionally requires--environment, so an unattended watch never approves agate that didn't exist when it started
ExecuteContext/cmd.Context()) stops the watch cleanly without decidinganything further
--watch --dry-runintentionally falls through to the existing non-looping preview (printscurrently-pending gates only), rather than looping in dry-run mode
--json, events stream as compact single-line NDJSON (one JSON object per gatedecided/skipped/errored, plus a final
run_done), notui.PrintJSON's pretty-printed formBehaviour notes
pkg/workflow/watch.go(WatchApprovals), reusing the existingservice.GetWorkflowRun/ListPendingDeployments/ReviewPendingDeploymentsand thepackage's existing
latestRunIDresolution — no duplication of the non-watch approve path.--environment's allowlist is left genuinely pending (not marked "seen"), so alater invocation that adds it to the allowlist picks it up automatically; it's reported once,
not on every poll.
gate_errorevent and does not stop the watch; a 401/403 does stop it (auth is unlikely to recover mid-loop).
internal/testutils: addedMockGitHubServer.SetResponseSequenceso tests can simulate a run'sstate changing across successive polls (falls back to the existing
SetResponsewhen nosequence is configured).
Testing
pkg/workflow/watch_test.go: 13 tests covering the approve/reject loop, sequential gates,decline-then-continue, the
--yes+--environmentallowlist requirement, non-reviewer/outside-allowlist skipping and single-report behaviour, concurrent-decision and auth-error handling,
Ctrl-C cancellation, timeout, and latest-run resolution.
internal/testutils/mock_server_test.go: 4 tests forSetResponseSequence's advance/repeat/fallback/priority behaviour.
cmd/workflow/workflow_test.go: 8 new tests covering flag validation (single-repo requirement,--yeswithout--environment, minimum--interval, non-TTY without--yes), the--yesend-to-end path, the interactive-confirm accept and decline+timeout paths, and NDJSON--jsonoutput shape.gofmt -l .,go vet ./...,go build ./...,go test -race ./...all clean;./scripts/check-coverage.shreports 93.7% (floor 85%).workflow approve, which was live-tested) — the session'sSGH_TOKENmay lack theDeployments-write/reviewer scope needed, per the project's noted token-permission caveat. The
non-watch path this reuses (
GetWorkflowRun,ListPendingDeployments,ReviewPendingDeployments) was already live-verified for workflow approve: approve or reject pending deployment gates for a run #20.Note on base branch
This PR is intentionally based on
feat/1-keyring-token-storage(#62), notdevelop— stackedper request while #1 is still being manually tested. It will need rebasing/retargeting to
developonce #1 merges.Closes #26
https://claude.ai/code/session_01Wy6Fqe5usEojMuNcAjKvRD