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

Copilot AI 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)

if isCI || cli.IsRunningInCI() {
	// non-interactive path
}

Copilot AI and 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

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Triage Assessment

Field Value
Category feature
Risk 🟢 Low
Score 35/100 (Impact 15 + Urgency 8 + Quality 12)
Action defer (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 ·

Copilot AI and 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>
Copilot AI changed the title [WIP] Add GetBoolFromEnv and GetStringFromEnv to envutil Extend envutil for bool/string access and remove direct CI bypasses Jul 7, 2026
Copilot AI requested a review from pelikhan July 7, 2026 19:17
@pelikhan pelikhan marked this pull request as ready for review July 8, 2026 01:38
Copilot AI review requested due to automatic review settings July 8, 2026 01:38

Copilot AI 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
File Description
pkg/envutil/envutil.go Adds GetBoolFromEnv/GetStringFromEnv and shared warning helper.
pkg/envutil/README.md Documents new helpers and their logging/defaulting contracts.
pkg/envutil/spec_test.go Adds spec-style tests for bool/string documented behavior.
pkg/envutil/envutil_test.go Adds unit tests for bool/string helpers.
pkg/parser/github.go Routes token env lookup through envutil.GetStringFromEnv.
pkg/parser/github_wasm.go Uses 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.go Centralizes Codespaces env access via GetStringFromEnv.
pkg/cli/add_interactive_workflow.go Uses 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 thread pkg/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 thread pkg/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 thread pkg/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 thread pkg/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-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot 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

github-actions Bot commented Jul 8, 2026

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)
Metric Value
Analyzed 26 (Go: 26)
✅ Design 24 (92.3%)
⚠️ Implementation 2 (7.7%)
Edge/error coverage 26 (100%)
Duplicate clusters 0
Inflation No (ratio 1.82:1)
🚨 Violations 0

Test Classification

Test File Classification Notes
TestGetIntFromEnv envutil_test.go Design Table-driven, 10 scenarios covering happy path, boundaries, errors
TestGetIntFromEnv_EdgeCases envutil_test.go Design Table-driven, 10 edge cases: overflow, whitespace, scientific notation, hex
TestGetIntFromEnv_WithoutLogger envutil_test.go Design Nil safety check (no panic on nil logger)
TestGetIntFromEnv_Idempotent envutil_test.go Design Deterministic behavior guarantee (3x calls)
TestGetIntFromEnv_BoundaryValidation envutil_test.go Design Table-driven, 4 boundary scenarios (inclusive min/max)
TestGetIntFromEnv_EmptyString envutil_test.go Design Empty string vs unset variable distinction
TestGetBoolFromEnv envutil_test.go Design Table-driven, 5 scenarios (true, false, numeric, invalid, unset)
TestGetStringFromEnv envutil_test.go Design Table-driven, 3 scenarios (unset, empty, value)
10× TestSpec_PublicAPI_* (GetIntFromEnv) spec_test.go Design Specification contract tests with explicit doc comments
4× TestSpec_PublicAPI_* (GetBoolFromEnv) spec_test.go Design Specification contract tests for boolean parsing
4× TestSpec_PublicAPI_* (GetStringFromEnv) spec_test.go Design Specification 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-actions github-actions Bot 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

github-actions Bot commented Jul 8, 2026

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-actions github-actions Bot 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:

func isRunningInCodespace() bool {
	isCodespace := envutil.GetBoolFromEnv(&quot;CODESPACES&quot;, false, codespaceLog)
	…

</details>

<details><summary>pkg/parser/github.go:67</summary>

**[/codebase-design]** Double-logging on the success path: `GetStringFromEnv` already logs `Using GITHUB_TOKEN from environment` via `githubLog`, then line 67 logs `Found GITHUB_TOKEN environment variable`. The caller gets two log lines for the same event.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Remove the now-redundant explicit log lines (67 and 71) and let `GetStringFromEnv` provide the single log signal:

```go
if 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 (`Returns defaultValue when the variable is absent or empty`) is accurate, but the MUST bullet on this line (`when the environment variable is not set`) 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 github-actions Bot 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:

func isRunningInCodespace() bool {
…

</details>

<details><summary>pkg/envutil/envutil.go:221</summary>

**Warning comment in `warn` docstring is inaccurate`GetStringFromEnv` does not call `warn`**

The function comment says the helper is &quot;shared by `GetIntFromEnv` and `GetBoolFromEnv`&quot;. That is correct, but it is worth noting explicitly (or updating the comment) that `GetStringFromEnv` does **not** use `warn` (string values cannot be &quot;invalid&quot;). This comment is a minor accuracy gap, but if `GetStringFromEnv` is later extended to emit a warning for some case, a reader might be misled into think</details>

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

Copilot AI commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@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.

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

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.4

Sign up for free to 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