Skip to content

Extend envutil for bool/string access and remove direct CI bypasses - #44097

Merged
pelikhan merged 11 commits into
mainfrom
copilot/deep-report-extend-envutil
Jul 8, 2026
Merged

Extend envutil for bool/string access and remove direct CI bypasses#44097
pelikhan merged 11 commits into
mainfrom
copilot/deep-report-extend-envutil

Conversation

CopilotAI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This change completes more of the os.Getenv centralization work by adding bool and string helpers to pkg/envutil, then using them in the remaining related callsites. It also removes same-package CI bypasses in pkg/cli so CI detection flows through IsRunningInCI() consistently.

  • envutil API

    • Adds GetBoolFromEnv(envVar string, defaultValue bool, debugLog *logger.Logger) bool
    • Adds GetStringFromEnv(envVar, defaultValue string, debugLog *logger.Logger) string
    • Reuses shared warning/logging behavior so invalid typed env values fall back consistently
  • CLI CI detection

    • Replaces the three direct os.Getenv("CI") checks in pkg/cli with IsRunningInCI()
    • Keeps GO_TEST_MODE and CODESPACES access on envutil-backed paths while preserving existing behavior where it matters
  • Related env access cleanup

    • Routes GitHub token env lookup in parser code through GetStringFromEnv
    • Updates Codespaces detection to use centralized env access instead of raw os.Getenv
  • Spec and package docs

    • Extends pkg/envutil README to document the new helpers alongside GetIntFromEnv
    • Adds unit/spec coverage for bool and string behavior, including defaulting, parsing, and logging contracts

Example:

isCI:=envutil.GetBoolFromEnv("CI", false, log)
token:=envutil.GetStringFromEnv("GITHUB_TOKEN", "", log)
ifisCI||cli.IsRunningInCI() {
// non-interactive path
}

CopilotAIand others added 5 commits July 7, 2026 18:43
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Triage Assessment

FieldValue
Categoryfeature
Risk🟢 Low
Score35/100 (Impact 15 + Urgency 8 + Quality 12)
Actiondefer (draft)

Summary: Adds GetBoolFromEnv and GetStringFromEnv to envutil with tests. Draft (+342/−27, 11 files). No CI triggered.

Next: Convert to ready-for-review, trigger CI.

Generated by 🔧 PR Triage Agent · 97.9 AIC · ⌖ 12.4 AIC · ⊞ 5.4K ·

CopilotAIand others added 3 commits July 7, 2026 19:07
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
CopilotAI changed the title [WIP] Add GetBoolFromEnv and GetStringFromEnv to envutilExtend envutil for bool/string access and remove direct CI bypassesJul 7, 2026
CopilotAI requested a review from pelikhanJuly 7, 2026 19:17
@pelikhan
pelikhan marked this pull request as ready for review July 8, 2026 01:38
CopilotAI review requested due to automatic review settings July 8, 2026 01:38

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends pkg/envutil to provide centralized boolean and string environment variable accessors (alongside the existing int helper), then migrates remaining callsites away from direct os.Getenv usage—especially around CLI CI/Codespaces detection and GitHub token lookup.

Changes:

  • Add GetBoolFromEnv and GetStringFromEnv to pkg/envutil, sharing warning/logging behavior with existing helpers.
  • Replace remaining direct env checks in parser + CLI codepaths with envutil helpers and cli.IsRunningInCI().
  • Update pkg/envutil package spec/docs and add unit/spec coverage for the new helpers.
Show a summary per file
FileDescription
pkg/envutil/envutil.goAdds GetBoolFromEnv/GetStringFromEnv and shared warning helper.
pkg/envutil/README.mdDocuments new helpers and their logging/defaulting contracts.
pkg/envutil/spec_test.goAdds spec-style tests for bool/string documented behavior.
pkg/envutil/envutil_test.goAdds unit tests for bool/string helpers.
pkg/parser/github.goRoutes token env lookup through envutil.GetStringFromEnv.
pkg/parser/github_wasm.goUses envutil.GetStringFromEnv for wasm token lookup (nil logger).
pkg/cli/ci-related callsites (interactive.go, add_wizard_command.go, add_interactive_orchestrator.go)Removes direct CI getenv checks in favor of IsRunningInCI() and uses GetBoolFromEnv for GO_TEST_MODE.
pkg/cli/codespace.goCentralizes Codespaces env access via GetStringFromEnv.
pkg/cli/add_interactive_workflow.goUses centralized Codespaces detection helper.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 11/11 changed files
  • Comments generated: 4
  • Review effort level: Low

Comment threadpkg/parser/github.go
Comment on lines +66 to 72
if token := envutil.GetStringFromEnv("GITHUB_TOKEN", "", githubLog); token != "" {
githubLog.Print("Found GITHUB_TOKEN environment variable")
return token, nil
}
if token := os.Getenv("GH_TOKEN"); token != "" { //nolint:osgetenvlibrary
if token := envutil.GetStringFromEnv("GH_TOKEN", "", githubLog); token != "" {
githubLog.Print("Found GH_TOKEN environment variable")
return token, nil
Comment threadpkg/envutil/envutil_test.go Outdated
Comment on lines +382 to +389
originalValue := os.Getenv(testEnvVar)
defer func() {
if originalValue != "" {
os.Setenv(testEnvVar, originalValue)
} else {
os.Unsetenv(testEnvVar)
}
}()
Comment threadpkg/envutil/envutil_test.go Outdated
Comment on lines +447 to +454
originalValue := os.Getenv(testEnvVar)
defer func() {
if originalValue != "" {
os.Setenv(testEnvVar, originalValue)
} else {
os.Unsetenv(testEnvVar)
}
}()
Comment threadpkg/cli/codespace.go
Comment on lines 16 to 18
// GitHub Codespaces sets CODESPACES=true environment variable
isCodespace := strings.EqualFold(os.Getenv("CODESPACES"), "true") //nolint:osgetenvlibrary
isCodespace := strings.EqualFold(envutil.GetStringFromEnv("CODESPACES", "", codespaceLog), "true")
codespaceLog.Printf("Codespace detection: is_codespace=%v", isCodespace)
@github-actions

github-actionsBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actionsBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actionsBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

⚠️PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actionsBot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, make this PR merge-ready, address unresolved review feedback, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 9.99 AIC · ⌖ 9.17 AIC · ⊞ 4.7K ·
Comment /souschef to run again

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 96/100 — Excellent

Analyzed 26 test(s): 24 design, 2 implementation, 0 violation(s).

📊 Metrics (26 tests)
MetricValue
Analyzed26 (Go: 26)
✅ Design24 (92.3%)
⚠️ Implementation2 (7.7%)
Edge/error coverage26 (100%)
Duplicate clusters0
InflationNo (ratio 1.82:1)
🚨 Violations0

Test Classification

TestFileClassificationNotes
TestGetIntFromEnvenvutil_test.goDesignTable-driven, 10 scenarios covering happy path, boundaries, errors
TestGetIntFromEnv_EdgeCasesenvutil_test.goDesignTable-driven, 10 edge cases: overflow, whitespace, scientific notation, hex
TestGetIntFromEnv_WithoutLoggerenvutil_test.goDesignNil safety check (no panic on nil logger)
TestGetIntFromEnv_Idempotentenvutil_test.goDesignDeterministic behavior guarantee (3x calls)
TestGetIntFromEnv_BoundaryValidationenvutil_test.goDesignTable-driven, 4 boundary scenarios (inclusive min/max)
TestGetIntFromEnv_EmptyStringenvutil_test.goDesignEmpty string vs unset variable distinction
TestGetBoolFromEnvenvutil_test.goDesignTable-driven, 5 scenarios (true, false, numeric, invalid, unset)
TestGetStringFromEnvenvutil_test.goDesignTable-driven, 3 scenarios (unset, empty, value)
10× TestSpec_PublicAPI_* (GetIntFromEnv)spec_test.goDesignSpecification contract tests with explicit doc comments
4× TestSpec_PublicAPI_* (GetBoolFromEnv)spec_test.goDesignSpecification contract tests for boolean parsing
4× TestSpec_PublicAPI_* (GetStringFromEnv)spec_test.goDesignSpecification contract tests for string retrieval

✅ Verdict

PASS — 7.7% implementation tests (threshold: 30%).

Exceptional quality indicators:

  • ✅ 100% design/specification test coverage — every test validates API contract or behavioral guarantee
  • ✅ Comprehensive specification suite with documented contracts (spec_test.go)
  • ✅ Thorough edge-case coverage: boundaries, invalid inputs, nil safety, format variations
  • ✅ All assertions include descriptive messages showing expected vs. actual
  • ✅ No coding violations (no mock libraries, proper build tags, no inflation)
  • ✅ Test ratio 1.82:1 (122 test LOC / 67 production LOC) — efficient and intentional

This PR demonstrates expert test design: the split between exploratory table-driven tests (envutil_test.go) and formal specification tests (spec_test.go) creates a defense-in-depth testing strategy that will catch regressions and document the public API contract.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
allowed:
- defaults
- "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 15.1 AIC · ⌖ 12.8 AIC · ⊞ 6.8K ·
Comment /review to run again

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 96/100. 7.7% implementation tests (threshold: 30%).

…elpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (356 new lines in pkg/ directories) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/44097-extend-envutil-for-typed-env-access.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-44097: Extend envutil with Typed Helpers for Boolean and String Environment Variables

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 58.5 AIC · ⌖ 10.4 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on two design consistency issues and one test coverage gap.

📋 Key Themes & Highlights

Key Themes

  • Boolean env var handled as string (codespace.go): CODESPACES is a boolean env var but is read via GetStringFromEnv + strings.EqualFold, inconsistent with the new GetBoolFromEnv helper used for GO_TEST_MODE. This is the most important fix — it breaks the PR's own consistency goal.
  • Double-logging (parser/github.go): GetStringFromEnv already logs via the provided logger; the caller also logs immediately after, producing two log lines per token lookup.
  • Missing warning-path test for logger: TestGetBoolFromEnv only exercises the nil-logger path; the debugLog != nil + invalid-value warning branch is untested.
  • IsRunningInCI itself still uses raw os.Getenv: minor follow-up opportunity to complete the centralization story in ci.go.
  • README MUST-bullet for GetStringFromEnv omits the empty-string case in the contract description.

Positive Highlights

  • ✅ Clean extraction of the shared warn() helper — removes duplication between GetIntFromEnv and GetBoolFromEnv
  • ✅ Consistent strconv.ParseBool semantics (handles 1, t, TRUE, etc.) align with Go idiom
  • ✅ Secret-safe logging design for GetStringFromEnv (logs variable name only, never value) — well thought-through
  • ✅ Good test/spec coverage for the happy paths and default cases
  • ✅ README updates are thorough and match the behavioral contracts

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 105 AIC · ⌖ 5.55 AIC · ⊞ 6.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/codespace.go:17

[/codebase-design]CODESPACES is a boolean env var — using GetStringFromEnv + strings.EqualFold bypasses GetBoolFromEnv, breaking consistency with GO_TEST_MODE and silently rejecting valid spellings like &quot;1&quot; or &quot;TRUE&quot; that strconv.ParseBool handles.

<details>
<summary>💡 Suggested fix</summary>

Replace the string comparison with the centralised bool helper:

funcisRunningInCodespace() bool {
isCodespace:= envutil.GetBoolFromEnv(&quot;CODESPACES&quot;, false, codespaceLog)
…
</details><details><summary>pkg/parser/github.go:67</summary>**[/codebase-design]**Double-loggingonthesuccesspath: `GetStringFromEnv`alreadylogs`Using GITHUB_TOKEN from environment`via`githubLog`, thenline67logs`Found GITHUB_TOKEN environment variable`. Thecallergetstwologlinesforthesameevent.
&lt;details&gt;
&lt;summary&gt;💡 Suggestedfix&lt;/summary&gt;
Removethenow-redundantexplicitloglines (67and71) andlet`GetStringFromEnv`providethesinglelogsignal:
```goif token := envutil.GetStringFromEnv(&quot;GITHUB_TOKEN&quot;, &quot;&quot;, githubLog); toke…</details><details><summary>pkg/cli/add_interactive_orchestrator.go:23</summary>**[/codebase-design]** `IsRunningInCI` still calls raw `os.Getenv` for `CI`, `CONTINUOUS_INTEGRATION`, and `GITHUB_ACTIONS` (see `pkg/cli/ci.go`), meaning this PR centralizes some call sites but leaves the underlying `ci.go` function un-centralized. The refactor is incomplete — a future change could migrate `ci.go` to use `GetBoolFromEnv` for all three vars to close the gap.&lt;details&gt;&lt;summary&gt;💡 Context&lt;/summary&gt;Not a blocking issue for this PR since `ci.go` was pre-existing, but worth noti…</details><details><summary>pkg/envutil/envutil_test.go:437</summary>**[/tdd]** `TestGetBoolFromEnv` only passes `nil` as the logger — the warning-emission code path (when `debugLog!=nil` and the value is invalid) is never exercised. A logger-with-invalid-value test would confirm that warnings route through `debugLog.Printf` rather than to stderr.&lt;details&gt;&lt;summary&gt;💡 Suggested test case&lt;/summary&gt;```go{ name: &quot;invalid value with logger&quot;, envValue: &quot;invalid&quot;, defaultValue: true, expected: true, // pass a real logger and …</details><details><summary>pkg/envutil/README.md:178</summary>**[/codebase-design]** The documented contract says `GetStringFromEnv` returns `defaultValue` when the env var is *absent or empty*, but the comment only mentions &quot;not set&quot;. The README text at line 168 (`ReturnsdefaultValuewhenthevariableisabsentorempty`) is accurate, but the MUST bullet on this line (`whentheenvironmentvariableisnotset`) omits the empty-string case — a reader relying on the spec could miss that `CODESPACES=&quot;&quot;` triggers the default path.
&lt;details&gt;
&lt;summary&gt;💡 Sug…
</details>

github-actions[bot]
github-actionsBot previously requested changes Jul 8, 2026

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

The envutil API additions (GetBoolFromEnv, GetStringFromEnv) and the CI-detection centralisation are solid. The refactor is well-tested and the documentation is clear. Two correctness issues and one minor inaccuracy need addressing before merge.

Blocking:

  1. Double logging in parser/github.goGetStringFromEnv already logs via the passed debugLog, so the caller-side githubLog.Print("Found ... environment variable") produces a duplicate line for every token lookup. Remove the redundant log statements (or pass nil to GetStringFromEnv).
  2. isRunningInCodespace should use GetBoolFromEnv — the migration from os.Getenv(...) to GetStringFromEnv still wraps the result in strings.EqualFold(..., "true"), which misses CODESPACES=1, CODESPACES=T, etc. Use GetBoolFromEnv("CODESPACES", false, codespaceLog) to be consistent with the boolean pattern this PR establishes.

Non-blocking:
3. warn docstring — the comment lists only GetIntFromEnv and GetBoolFromEnv as users, which is accurate; just make it explicit that GetStringFromEnv intentionally does not call warn to avoid future confusion.

Overall assessment
  • API design: ✅ Clean, consistent with existing GetIntFromEnv
  • Test coverage: ✅ Good unit + spec coverage including logger path and invalid values
  • Documentation: ✅ README updated with correct behavioral contracts
  • CI bypass removal: ✅ All three direct os.Getenv("CI") checks replaced with IsRunningInCI()
  • GO_TEST_MODE migration: ✅ Correct use of GetBoolFromEnv
  • Wasm path: ✅ nil logger is intentional and documented

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 100.8 AIC · ⌖ 6.24 AIC · ⊞ 4.8K

Comments that could not be inline-anchored

pkg/parser/github.go:67

Redundant log after GetStringFromEnv

GetStringFromEnv with a non-nil debugLog already emits &quot;Using GITHUB_TOKEN from environment&quot; (see envutil.go line 326). The call on this line then emits a second log: &quot;Found GITHUB_TOKEN environment variable&quot;. The same duplication occurs for GH_TOKEN at line 71. Either remove both caller-side githubLog.Print(&quot;Found ... environment variable&quot;) statements, or pass nil instead of githubLog to GetStringFromEnv here and log only once at …

pkg/cli/codespace.go:17

isRunningInCodespace should use GetBoolFromEnv for consistency

This PR introduces GetBoolFromEnv specifically to replace ad-hoc boolean env var checks. isRunningInCodespace was migrated from strings.EqualFold(os.Getenv(...), &quot;true&quot;) to strings.EqualFold(GetStringFromEnv(...), &quot;true&quot;), but that still does not recognise the other values accepted by strconv.ParseBool (1, T, TRUE, etc.). The equivalent with the new helper would be:

funcisRunningInCodespace() bool {
…
</details><details><summary>pkg/envutil/envutil.go:221</summary>**Warningcommentin`warn`docstringisinaccurate`GetStringFromEnv`doesnotcall`warn`**Thefunctioncommentsaysthehelperis&quot;sharedby`GetIntFromEnv`and`GetBoolFromEnv`&quot;. Thatiscorrect, butitisworthnotingexplicitly (orupdatingthecomment) that`GetStringFromEnv`does**not**use`warn` (stringvaluescannotbe&quot;invalid&quot;). Thiscommentisaminoraccuracygap, butif`GetStringFromEnv`islaterextendedtoemitawarningforsomecase, areadermightbemisledintothink</details>

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

CopilotAI commented Jul 8, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please run the pr-finisher skill, make this PR merge-ready, address unresolved review feedback, and rerun checks once the branch is up to date.

...

Addressed the unresolved review feedback in 8d278fe: removed the duplicate token/Codespaces env logs and fixed the new envutil tests to restore unset vs empty env vars correctly. Local validation passed (make fmt, make lint, make test-unit, make test, make recompile, make agent-report-progress). The branch is updated, but the GitHub checks on the new head are still action_required with no jobs started, so a maintainer will need to re-trigger/approve CI from GitHub before merge.

CopilotAI requested a review from gh-aw-botJuly 8, 2026 02:12
@pelikhan
pelikhan merged commit 15dc4d9 into mainJul 8, 2026
29 checks passed
@pelikhan
pelikhan deleted the copilot/deep-report-extend-envutil branch July 8, 2026 02:18
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.4

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] Extend envutil with GetBoolFromEnv/GetStringFromEnv and migrate os.Getenv("CI") bypasses

4 participants

@gh-aw-bot@pelikhan