Skip to content

fix(cli): let a second Ctrl-C abort the GCP key cleanup - #2240

Merged
cristim merged 1 commit into
mainfrom
fix/gcp-second-interrupt
Oct 16, 2026
Merged

cristim merged 1 commit into
mainfrom
fix/gcp-second-interrupt

Conversation

@cristim

@cristim cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Refs #2117 (item 3). Items 1 and 2 merged in #2233. Item 4 (report a possibly-orphaned key when CreateKey is interrupted) follows separately.

Summary

After a first Ctrl-C, configure-gcp waits up to 60s to delete the minted service-account key remotely. A second Ctrl-C during that wait was swallowed, because the signal handler stayed registered until runConfigureGCP returned.

Changes:

  • beginUpload now has its own signal context. On the first signal, a goroutine calls stop(), which restores the default SIGINT handling, so a second Ctrl-C aborts at once.
  • The goroutine then prints a neutral line to stderr: Interrupted: finishing cleanup (up to 60s). Press Ctrl-C again to abort immediately.
  • An atomic finished flag stops this line from printing on a normal run.
  • deleteMintedGCPKeyRemotely prints the exact gcloud iam service-accounts keys delete ... command just before it starts the remote delete. If the operator aborts, they already have the cleanup command.
  • The local key file is now removed before the slow remote delete starts. An abort therefore leaves no plaintext key on disk, only the remote key whose delete command was printed. A removal error is still reported on every path.
  • The PR A semantics hold: after a successful upload whose local removal failed, the stored key stays active in GCP.

Evidence (local, macOS, GOTOOLCHAIN=go1.26.9, signal-test harness with an httptest fixture, no GCP calls)

  • New TestRunConfigureGCP_SecondInterruptAbortsSlowDelete:
    • The child's DELETE handler blocks.
    • The test waits for key.json, sends SIGINT, then waits for the "Interrupted" line and the gcloud command.
    • It asserts the local key directory is gone, sends a second SIGINT, and asserts the child is killed by SIGINT within 5s.
  • New TestRunConfigureGCP_SuccessPrintsNoInterruptText: a successful run prints neither line.
  • Fail before:
    • With the configure_gcp.go change stashed, the second-interrupt test times out ("waiting for Interrupted message; child still running").
    • With the old cleanup order, it fails with "local key must be removed before the slow remote delete".
  • Stability: the second-interrupt test passes -race -count=20 (author) and 30/30 runs (reviewer). -run TestRunConfigureGCP_ -count=5 passes, including the existing blocked-stdout and interrupt tests.
  • Checks:
    • make build: passes.
    • go vet ./...: passes.
    • go test -race ./cmd/...: passes.
    • golangci-lint v2.10.1: 0 issues.
    • scripts/test-git-secrets-allowlist.sh: 44/44.
  • Review: an independent non-author reviewer (sonnet, high) did three passes.
    • Pass 1 found that the plaintext key file was left on disk after an abort. It is fixed by the cleanup reorder.
    • Pass 2 found a timing flake in the new test. It is fixed by waiting for key.json.
    • The final pass found no actionable findings at e07c3992.

🤖 Generated with Claude Code

After the first interrupt the signal handler stayed installed, so a
second Ctrl-C was swallowed during the up-to-60s remote key delete.
Restore default handling once the context is canceled and print a
neutral notice on stderr (skipped on normal completion). The remote
delete now prints the manual gcloud delete command before it starts.

Adds a subprocess test where the second SIGINT, keyed on the notice,
kills a hung delete, and a success-path test that prints nothing extra.

Refs #2117

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cristim cristim added priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/internal Team-internal only effort/xs Trivial / one-liner type/chore Maintenance / non-user-visible triaged Item has been triaged labels Oct 16, 2026
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 2 billable files and costs up to $0.50.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 9 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 65 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-cli/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: b0e004d5-8294-47ff-bd0d-060b07d5da08
📥 Commits

Reviewing files that changed from the base of the PR and between 6a15af9 and c9a6b37.

📒 Files selected for processing: 2
  • cmd/configure_gcp.go
  • cmd/configure_gcp_signal_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

Independent review verdict: CLEAN at c9a6b37

Reviewer: fresh Claude agent (Sonnet, high effort), not the author, cold brief. The PR body was treated as a claim. Coordinator: claude-cc-reviewer.

Blocking findings: none.

Verified:

  • Default SIGINT is really restored. The only non-test signal registrations are cmd/configure_gcp.go:172 and cmd/main.go:406. The second one (invocationContext) is used only by runTool, which is a different command. So once stop() runs, nothing else holds SIGINT, and the new test sees the child die by SIGINT on the second Ctrl-C.
  • No spurious message or goroutine leak. finish() sets finished=true before release(), so a normal completion never prints the "Interrupted" line, and the goroutine exits when the signal context is done.
  • The first Ctrl-C still cleans up. deleteMintedGCPKeyRemotely uses context.WithTimeout(context.Background(), ...), not the cancelled signal context. A delete failure still returns the error with the gcloud ... keys delete command.
  • An abort leaves the user a way to clean up. The gcloud delete command is printed to stderr before the remote delete starts, and the process dies by signal with a non-zero exit. The local key is removed before the remote delete, so no plaintext key stays on disk.

What was attacked and how (GOTOOLCHAIN=go1.26.9, no GCP calls):

  • go vet ./cmd/... is clean. go test -race -count=1 ./cmd/... passes 1521.
  • The two new tests ran with -race -count=20: 40 passed, no flakes.
  • Fail-before: with configure_gcp.go reverted to main, the second-interrupt test times out with the child still running.
  • Mutation probes, both killed:
    • removing stop() in the goroutine;
    • dropping the finished check.

Gate: there is no closing issue (the PR refs #2117 item 3) and it carries its own triage labels. Merge once CI is green at this SHA.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member Author

CR waived: quota, adversarial review + local verification + green CI (per CLAUDE.md:172-173). CodeRabbit posted 'Review limit reached' on this PR. Gate at c9a6b37: CLEAN independent verdict above, CI green, mergeability CLEAN. (claude-cc-reviewer)

@cristim
cristim merged commit 686f804 into main Oct 16, 2026
12 checks passed
@cristim
cristim deleted the fix/gcp-second-interrupt branch October 16, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant