-
Notifications
You must be signed in to change notification settings - Fork 6
Refactor duplicate environment variable getters into reusable envutil package #816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…tters - Created internal/envutil package with GetEnvString, GetEnvInt, and GetEnvBool utilities - Added comprehensive test coverage for envutil package (46 test cases) - Refactored flags_logging.go to use envutil (reduced from ~35 lines to ~12 lines) - Refactored flags_difc.go to use envutil (reduced from ~9 lines to ~2 lines) - All existing tests pass Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
All tests pass including make agent-finished verification Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Refactors duplicated “env var + default fallback” logic used by CLI flag default getters into a shared internal/envutil package, so environment parsing/validation is centralized and easier to reuse across internal/cmd.
Changes:
- Added
internal/envutilhelpers:GetEnvString,GetEnvInt(positive int),GetEnvBool(truthy/falsy set). - Updated
internal/cmdflag default getters (logging + DIFC) to callenvutilinstead of duplicating logic. - Added unit tests for the new
envutilhelpers.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/envutil/envutil.go | Introduces shared env var getter utilities (string/int/bool). |
| internal/envutil/envutil_test.go | Adds test coverage for env var parsing and defaults. |
| internal/cmd/flags_logging.go | Refactors logging-related default getters to use envutil. |
| internal/cmd/flags_difc.go | Refactors DIFC default getter to use envutil. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if value, err := strconv.Atoi(envValue); err == nil && value > 0 { | ||
| return value |
Copilot
AI
Feb 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetEnvInt previously used fmt.Sscanf("%d") in the flag getter, which accepts leading/trailing whitespace (and newlines). Switching to strconv.Atoi changes behavior so values like " 4096 " will now be treated as invalid and fall back to default. To avoid a subtle regression for env var parsing, trim surrounding whitespace before Atoi (and keep the >0 validation).
| if value, err := strconv.Atoi(envValue); err == nil && value > 0 { | |
| return value | |
| envValue = strings.TrimSpace(envValue) | |
| if envValue != "" { | |
| if value, err := strconv.Atoi(envValue); err == nil && value > 0 { | |
| return value | |
| } |
| func TestGetEnvInt(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| envKey string | ||
| envValue string | ||
| setEnv bool | ||
| defaultValue int | ||
| expected int | ||
| }{ | ||
| { | ||
| name: "env var set with valid positive int - returns env value", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "2048", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 2048, | ||
| }, | ||
| { | ||
| name: "env var not set - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| setEnv: false, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| { | ||
| name: "env var empty string - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| { | ||
| name: "env var with non-numeric value - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "invalid", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| { | ||
| name: "env var with negative value - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "-100", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| { | ||
| name: "env var with zero - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "0", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| { | ||
| name: "env var with very large int - returns env value", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "10240", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 10240, | ||
| }, | ||
| { | ||
| name: "env var with small positive int - returns env value", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "1", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1, | ||
| }, | ||
| { | ||
| name: "env var with float value - returns default", | ||
| envKey: "TEST_INT_VAR", | ||
| envValue: "123.45", | ||
| setEnv: true, | ||
| defaultValue: 1024, | ||
| expected: 1024, | ||
| }, | ||
| } |
Copilot
AI
Feb 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GetEnvInt tests don't cover whitespace-padded values (e.g. " 2048 ", "2048\n"), which were accepted by the previous implementation using fmt.Sscanf("%d"). Add a couple of cases to lock in the intended behavior (either accept via TrimSpace or explicitly document/reject) so future refactors don't reintroduce parsing regressions.
Automated analysis reported ~200 lines of duplicate code across 3 patterns. Investigation shows all significant duplication already eliminated through prior refactoring. ## Findings **Pattern 1: Logger initialization (~120 lines)** ✅ Resolved - Generic helpers: `initGlobalLogger[T]()`, `closeGlobalLogger[T]()` in `global_helpers.go` - Generic file init: `initLogger[T]()` with customizable fallback in `common.go` - Handles all 4 logger types (FileLogger, JSONLLogger, MarkdownLogger, ServerFileLogger) **Pattern 2: Flag env getters (~40 lines)** ✅ Resolved - Extracted to `internal/envutil` package in PR #816 - `GetEnvString()`, `GetEnvInt()`, `GetEnvBool()` with validation - All flag files migrated **Pattern 3: Global log functions (~96 lines)**⚠️ By design - Thin public API wrappers (8 lines each, 119+ call sites) - `markdown_logger.go` already uses `logWithMarkdown()` helper - `server_file_logger.go` has different signature and dual-logging behavior - Standard Go idiom for stable APIs ## Result 160 lines of actual duplication eliminated. Remaining "duplication" is intentional thin wrappers following Go best practices. No code changes required. > [!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-build1073420489/b275/launcher.test /tmp/go-build1073420489/b275/launcher.test -test.testlogfile=/tmp/go-build1073420489/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go --global 64/pkg/tool/linux_amd64/asm pull.rebase` (dns block) > - `invalid-host-that-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build1073420489/b260/config.test /tmp/go-build1073420489/b260/config.test -test.testlogfile=/tmp/go-build1073420489/b260/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go ternal/fips140/d--64 64/pkg/tool/linu-o user.name` (dns block) > - `nonexistent.local` > - Triggering command: `/tmp/go-build1073420489/b275/launcher.test /tmp/go-build1073420489/b275/launcher.test -test.testlogfile=/tmp/go-build1073420489/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go --global 64/pkg/tool/linux_amd64/asm pull.rebase` (dns block) > - `slow.example.com` > - Triggering command: `/tmp/go-build1073420489/b275/launcher.test /tmp/go-build1073420489/b275/launcher.test -test.testlogfile=/tmp/go-build1073420489/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go --global 64/pkg/tool/linux_amd64/asm pull.rebase` (dns block) > - `this-host-does-not-exist-12345.com` > - Triggering command: `/tmp/go-build1073420489/b284/mcp.test /tmp/go-build1073420489/b284/mcp.test -test.testlogfile=/tmp/go-build1073420489/b284/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true _.a ternal/fips140/ed25519/cast.go 64/pkg/tool/linux_amd64/compile` (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>[duplicate-code] Duplicate Code Analysis Report</issue_title> > <issue_description># 🔍 Duplicate Code Analysis Report > > *Analysis of commit db3ff1f* > > ## Summary > > This analysis identified **3 significant duplication patterns** in the codebase that impact maintainability and create opportunities for refactoring. The patterns span multiple packages including `internal/logger` and `internal/cmd`, with a total of **~200+ lines of duplicated or highly similar code**. > > ## Detected Patterns > > This analysis found 3 significant duplication patterns: > > 1. **Logger Initialization Boilerplate** - Severity: High - See sub-issue #aw_abc123def456 > 2. **Flag Environment Variable Getters** - Severity: Medium - See sub-issue #aw_789ghi012jkl > 3. **Global Logging Function Repetition** - Severity: Medium - See sub-issue #aw_345mno678pqr > > ## Overall Impact > > - **Total Duplicated Lines**: ~200+ lines of similar/duplicated code > - **Affected Files**: 8 Go files across 2 packages (logger, cmd) > - **Maintainability Risk**: High - Changes to patterns require updates across multiple files > - **Refactoring Priority**: High for Pattern 1, Medium for Patterns 2-3 > > ## Key Findings > > ### 1. Logger Package (High Priority) > - **4 logger files** share nearly identical initialization patterns > - Each logger reimplements: file opening, error handling, mutex setup, global state management > - Total duplication: ~120+ lines across FileLogger, JSONLLogger, MarkdownLogger, ServerFileLogger > > ### 2. Flag Package (Medium Priority) > - **3 getDefault* functions** with identical structure in `flags_logging.go` > - Additional similar function in `flags_difc.go` > - Pattern: Check env var → parse/validate → fallback to default > - Total duplication: ~40+ lines > > ### 3. Global Logging Functions (Medium Priority) > - **4 LogLevel functions** (Info/Warn/Error/Debug) repeated across 3 logger files > - Each set: 4 functions × ~8 lines = ~32 lines per file > - Total duplication: ~96+ lines across file_logger.go, markdown_logger.go, server_file_logger.go > > ## Next Steps > > 1. Review individual pattern sub-issues for detailed analysis > 2. Prioritize refactoring based on severity and impact (start with Pattern 1) > 3. Create implementation plan for highest priority patterns > 4. Consider extracting common utilities to shared helper functions > > ## Analysis Metadata > > - **Analyzed Files**: 68 Go files (non-test) > - **Detection Method**: Serena semantic code analysis + pattern matching > - **Commit**: db3ff1f > - **Analysis Date**: 2026-02-07T10:08:14.613Z > - **Trigger**: Recent commit adding configurable payload size threshold (#790) > > > > > > AI generated by [Duplicate Code Detector](https://github.com/github/gh-aw-mcpg/actions/runs/21778382116) > > - [x] expires <!-- gh-aw-expires: 2026-02-14T10:13:16.858Z --> on Feb 14, 2026, 10:13 AM UTC > > <!-- gh-aw-agentic-workflow: Duplicate Code Detector, engine: copilot, run: https://github.com/github/gh-aw-mcpg/actions/runs/21778382116 --> > > <!-- gh-aw-workflow-id: duplicate-code-detector --></issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> > **Custom agent used: agentic-workflows** > GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #797 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
Four
getDefault*()functions ininternal/cmdduplicated the same environment variable getter pattern with minor variations (string, int, bool).Changes
Created
internal/envutilpackage with generic environment variable utilities:GetEnvString(key, default)- string valuesGetEnvInt(key, default)- integer values with positive validationGetEnvBool(key, default)- boolean values (supports:1/true/yes/on,0/false/no/off)Refactored flag getters to use new utilities:
flags_logging.go: 3 getters (log dir, payload dir, payload size threshold)flags_difc.go: 1 getter (DIFC enable flag)Added comprehensive test coverage (46 test cases covering edge cases and validation)
Before/After
Impact
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
example.com/tmp/go-build542317655/b279/launcher.test /tmp/go-build542317655/b279/launcher.test -test.testlogfile=/tmp/go-build542317655/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo(dns block)invalid-host-that-does-not-exist-12345.com/tmp/go-build542317655/b264/config.test /tmp/go-build542317655/b264/config.test -test.testlogfile=/tmp/go-build542317655/b264/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo 64/src/encoding/pem/pem.go x_amd64/vet(dns block)nonexistent.local/tmp/go-build542317655/b279/launcher.test /tmp/go-build542317655/b279/launcher.test -test.testlogfile=/tmp/go-build542317655/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo(dns block)slow.example.com/tmp/go-build542317655/b279/launcher.test /tmp/go-build542317655/b279/launcher.test -test.testlogfile=/tmp/go-build542317655/b279/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/runtime/cgo(dns block)this-host-does-not-exist-12345.com/tmp/go-build542317655/b288/mcp.test /tmp/go-build542317655/b288/mcp.test -test.testlogfile=/tmp/go-build542317655/b288/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true ache/go/1.25.6/x64/src/runtime/c-errorsas .cfg tnet/tools/as(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
This section details on the original issue you should resolve
<issue_title>[duplicate-code] Duplicate Code Pattern: Flag Environment Variable Getters</issue_title>
<issue_description># 🔍 Duplicate Code Pattern: Flag Environment Variable Getters
Part of duplicate code analysis: #797
Summary
Multiple
getDefault*()functions in theinternal/cmdpackage follow an identical pattern for retrieving configuration values from environment variables with fallback to hardcoded defaults. This pattern is repeated 4 times across flag files with only minor variations.Duplication Details
Pattern: Environment Variable Getter Functions
internal/cmd/flags_logging.go:getDefaultLogDir()(lines 36-41)getDefaultPayloadDir()(lines 45-50)getDefaultPayloadSizeThreshold()(lines 54-64)internal/cmd/flags_difc.go:getDefaultEnableDIFC()(lines 30-38)Duplicated Structure
All functions follow the same pattern:
Code Examples
String Getter (getDefaultLogDir):
String Getter (getDefaultPayloadDir) - Nearly identical:
Integer Getter (getDefaultPayloadSizeThreshold):
Boolean Getter (getDefaultEnableDIFC):
Impact Analysis
Maintainability
Bug Risk
> 0, but what about negative values elsewhere?Code Bloat
Refactoring Recommendations
1. Create Generic Environment Getter Utility
Effort: Low-Medium (2-4 hours)
Extract common pattern into type-safe generic utility: