Commit 432703b
authored
Implement Cobra v1.10.0 improvements: context support, declarative flag validation, and lifecycle hooks (#882)
Implements Priority 1 and 2 recommendations from Go Fan report for
github.com/spf13/cobra v1.10.2. Leverages new Cobra features for cleaner
CLI code and better reliability.
## Context-based graceful shutdown
Replace manual signal handling with `signal.NotifyContext` for proper
cancellation propagation:
```go
// Before: manual signal channel
ctx, cancel := context.WithCancel(context.Background())
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// After: v1.10.0 pattern
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
```
- HTTP server shutdown with 5s timeout via `httpServer.Shutdown(ctx)`
- Context cancellation propagates through unified server
## Declarative flag validation
Replace manual preRun validation with Cobra's built-in validation
groups:
```go
// Flag validation groups
cmd.MarkFlagsMutuallyExclusive("routed", "unified")
cmd.MarkFlagsOneRequired("config", "config-stdin")
```
- Removes 5 lines of manual validation logic from `preRun`
- Produces consistent error messages: `"at least one of the flags in the
group [config config-stdin] is required"`
- Updates integration test to expect new error format
## Lifecycle hooks
- Add `PersistentPostRun` hook for logger cleanup
- Moves defer calls from `run()` to `postRun()` for cleaner separation
- Logger initialization in run, cleanup in postRun
## Enhanced completions
Add ActiveHelp hints for better shell completion UX:
```go
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return cobra.AppendActiveHelp(nil,
"Tip: Use --config <file> for file-based config or --config-stdin for piped JSON config"),
cobra.ShellCompDirectiveNoFileComp
}
```
## Version template
Custom formatting: `MCPG Gateway {{.Version}}` instead of default
output.
## Tests
- Context cancellation behavior
- Flag validation group registration
- Version template and postRun hook presence
> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `example.com`
> - Triggering command: `/tmp/go-build884163099/b275/launcher.test
/tmp/go-build884163099/b275/launcher.test
-test.testlogfile=/tmp/go-build884163099/b275/testlog.txt
-test.paniconexit0 -test.timeout=10m0s -test.v=true go 95WK6prK2
x_amd64/vet` (dns block)
> - `invalid-host-that-does-not-exist-12345.com`
> - Triggering command: `/tmp/go-build884163099/b260/config.test
/tmp/go-build884163099/b260/config.test
-test.testlogfile=/tmp/go-build884163099/b260/testlog.txt
-test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a HEAD 64/bin/as
TOKEN"; }; f sto/tmp/go-build3472542465/b223/cmd.test go cal/bin/git
512block_amd64.o-test.timeout=10m0s 64/s�� 64/src/runtime/cgo xpwaXSmNs
ache/go/1.25.6/x64/pkg/tool/linu--64 --abbrev-ref 2542465/b013/
.12/x64/bin/git 05.o` (dns block)
> - `nonexistent.local`
> - Triggering command: `/tmp/go-build884163099/b275/launcher.test
/tmp/go-build884163099/b275/launcher.test
-test.testlogfile=/tmp/go-build884163099/b275/testlog.txt
-test.paniconexit0 -test.timeout=10m0s -test.v=true go 95WK6prK2
x_amd64/vet` (dns block)
> - `slow.example.com`
> - Triggering command: `/tmp/go-build884163099/b275/launcher.test
/tmp/go-build884163099/b275/launcher.test
-test.testlogfile=/tmp/go-build884163099/b275/testlog.txt
-test.paniconexit0 -test.timeout=10m0s -test.v=true go 95WK6prK2
x_amd64/vet` (dns block)
> - `this-host-does-not-exist-12345.com`
> - Triggering command: `/tmp/go-build884163099/b284/mcp.test
/tmp/go-build884163099/b284/mcp.test
-test.testlogfile=/tmp/go-build884163099/b284/testlog.txt
-test.paniconexit0 -test.timeout=10m0s -test.v=true g_.a HEAD
x_amd64/vet --abbrev-ref /bidi git x_amd64/vet 64/s��
64/src/runtime/cgo1.25.6 V8YDPttZl inux.go esnew.go ocknew.go nix_cgo.go
nix_cgo_res.go` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/github/gh-aw-mcpg/settings/copilot/coding_agent)
(admins only)
>
> </details>
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
----
*This section details on the original issue you should resolve*
<issue_title>[go-fan] Go Module Review:
github.com/spf13/cobra</issue_title>
<issue_description># 🐹 Go Fan Report: Cobra CLI Framework
## Module Overview
**Cobra** (github.com/spf13/cobra) is the industry-standard CLI
framework for Go, powering tools like kubectl, hugo, and GitHub CLI. It
provides a powerful structure for building modern command-line
applications with commands, subcommands, flags, and shell completions.
**Current Version**: v1.10.2 ✅ (Latest: v1.10.2, Dec 3, 2025)
**Repository**: https://github.com/spf13/cobra
**Popularity**: 40k+ stars
## Current Usage in gh-aw-mcpg
**Well-Implemented** ✅
The project uses Cobra appropriately across 7 files in `internal/cmd/`:
### Files & Structure
- **root.go** - Root command definition and CLI entry point
- **completion.go** - Shell completion commands (bash, zsh, fish,
powershell)
- **flags*.go** (5 files) - Well-organized flag definitions by domain:
- `flags_core.go` - Core configuration flags
- `flags_logging.go` - Logging flags
- `flags_difc.go` - DIFC feature flags (properly uses `MarkHidden()`)
- `flags_launch.go` - Launch configuration
- `flags.go` - Registration helpers
### Key Patterns Observed
✅ Clean command structure without unnecessary nesting
✅ Flags well-organized by functional area
✅ Proper use of `MarkHidden()` for experimental features
✅ Comprehensive shell completion support
✅ Version command integration
## Research Findings
### Recent Cobra Updates (v1.10.x)
#### v1.10.2 (Dec 2025) - Current Version
- **Dependency Cleanup**: Migrated from deprecated `gopkg.in/yaml.v3` to
`go.yaml.in/yaml/v3`
- Significantly cleaner dependency chain
- No action required (transparent upgrade)
- Performance improvements (vars → consts)
- Enhanced documentation for repeated flags
#### v1.10.0 (Sep 2025)
- **Context Support**: Commands can now receive and use context for
cancellation/timeout
- **Customizable ShellCompDirective**: Per-command completion behavior
- Improved map flag completions
#### v1.9.0 (Feb 2025)
- **Linker Deadcode Elimination**: Smaller binaries by removing unused
code
- **CompletionFunc Type**: Cleaner completion code
- **CompletionWithDesc Helper**: Easier completions with descriptions
- **ActiveHelp**: Context-sensitive help during tab completion
### Best Practices from Cobra Maintainers
1. **Error Handling**: Use `RunE` instead of `Run` to return errors
properly
2. **Flag Validation**: Use built-in flag groups instead of manual
validation
3. **Context Usage**: Pass context to commands for cancellation and
timeouts
4. **Completions**: Implement dynamic completions for better UX
5. **Lifecycle Hooks**: Use Pre/Post Run hooks for setup and teardown
## Improvement Opportunities
### 🏃 Quick Wins (High Impact, Low Effort)
#### 1. Add Context Support (v1.10.0 feature)
**Priority**: HIGH | **Effort**: LOW
**Current**: No evidence of context usage for graceful shutdown
**Opportunity**: Enable proper cancellation and timeout handling
``````go
// In root.go
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
rootCmd.SetContext(ctx)
// In command RunE
func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context() // Get context with cancellation support
// Use ctx for HTTP requests, goroutines, etc.
return server.Run(ctx)
}
``````
**Benefits**:
- ✅ Proper graceful shutdown on SIGINT/SIGTERM
- ✅ Timeout handling for long-running operations
- ✅ Request tracing and cancellation propagation
- ✅ Better testability with context-based timeouts
#### 2. Use Flag Validation Groups
**Priority**: HIGH | **Effort**: LOW
**Current**: Manual flag validation in code
**Opportunity**: Declarative validation with better error messages
``````go
// Mutually exclusive flags
cmd.MarkFlagsMutuallyExclusive("config", "stdin-config")
// Flags required together
cmd.MarkFlagsRequiredTogether("log-dir", "enable-file-logging")
// At least one required
cmd.MarkFlagsOneRequired("config", "stdin-config")
``````
**Benefits**:
- ✅ Cleaner code (remove manual validation logic)
- ✅ Consistent, user-friendly error messages
- ✅ Self-documenting flag relationships
- ✅ Less maintenance burden
#### 3. Enhanced Dynamic Completions
**Priority**: MEDIUM | **Effort**: MEDIUM
**Current**: Static shell completions
**Opportunity**: Dynamic completions for config files, server IDs
``````go
// Config file completion
cmd.RegisterFlagCompletionFunc("config", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
configs, _ := filepath.Glob("*.toml")
suggestions := []string{}
for _, c := range configs {
suggestions = append(suggestions, c+"\tTOML configuration file")
}
return suggestions, cobra.ShellCompDirectiveDefault
})
// Server ID completion (from loaded config)
cmd.RegisterFlagCompletionFunc("server-id", func(cmd *co...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes #874File tree
4 files changed
+114
-40
lines changed- internal/cmd
- test/integration
4 files changed
+114
-40
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
45 | 45 | | |
| 46 | + | |
46 | 47 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| 52 | + | |
51 | 53 | | |
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| |||
91 | 97 | | |
92 | 98 | | |
93 | 99 | | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
94 | 108 | | |
95 | 109 | | |
96 | 110 | | |
97 | 111 | | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | 112 | | |
104 | 113 | | |
105 | 114 | | |
| |||
128 | 137 | | |
129 | 138 | | |
130 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
131 | 149 | | |
132 | | - | |
| 150 | + | |
| 151 | + | |
133 | 152 | | |
134 | 153 | | |
135 | 154 | | |
136 | 155 | | |
137 | 156 | | |
138 | 157 | | |
139 | | - | |
140 | 158 | | |
141 | 159 | | |
142 | 160 | | |
143 | 161 | | |
144 | 162 | | |
145 | | - | |
146 | 163 | | |
147 | 164 | | |
148 | 165 | | |
149 | 166 | | |
150 | 167 | | |
151 | | - | |
152 | 168 | | |
153 | 169 | | |
154 | 170 | | |
155 | 171 | | |
156 | 172 | | |
157 | | - | |
158 | 173 | | |
159 | 174 | | |
160 | 175 | | |
| |||
270 | 285 | | |
271 | 286 | | |
272 | 287 | | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
| 288 | + | |
277 | 289 | | |
278 | | - | |
| 290 | + | |
279 | 291 | | |
280 | 292 | | |
281 | | - | |
282 | 293 | | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | 294 | | |
287 | 295 | | |
288 | 296 | | |
| |||
329 | 337 | | |
330 | 338 | | |
331 | 339 | | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
332 | 348 | | |
333 | 349 | | |
334 | 350 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| 10 | + | |
9 | 11 | | |
| 12 | + | |
10 | 13 | | |
11 | 14 | | |
12 | 15 | | |
| |||
77 | 80 | | |
78 | 81 | | |
79 | 82 | | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
86 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
87 | 86 | | |
88 | 87 | | |
89 | 88 | | |
| |||
138 | 137 | | |
139 | 138 | | |
140 | 139 | | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
| 140 | + | |
| 141 | + | |
149 | 142 | | |
150 | 143 | | |
151 | 144 | | |
| |||
499 | 492 | | |
500 | 493 | | |
501 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
505 | | - | |
506 | | - | |
507 | | - | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
508 | 509 | | |
509 | 510 | | |
510 | 511 | | |
511 | | - | |
512 | | - | |
| 512 | + | |
| 513 | + | |
513 | 514 | | |
514 | 515 | | |
515 | 516 | | |
| |||
0 commit comments