Skip to content

Conversation

@qmuntal
Copy link
Member

@qmuntal qmuntal commented Feb 9, 2026

This pull request introduces support for embedding the GitHub Copilot CLI binary directly within Go applications using the SDK, removing the need for users to have the CLI installed separately. It adds a robust mechanism for embedding, unpacking, and managing the Copilot CLI binary, including versioning, file locking for concurrency, and hash validation. The changes also include comprehensive tests and documentation updates.

The bundling works as follows:

# One-time setup step per project
go get -tool github.com/github/copilot-sdk/go/cmd/bundler 

# Downloads the CLI
# Places it at the current directory
# Creates a Go file that embed the CLI
# Instruct the SDK to use it
go tool bundler

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Cross-SDK Consistency Review

Summary

This PR introduces CLI bundling/embedding capability exclusively to the Go SDK, creating a significant feature disparity across the SDK implementations. While this is an excellent feature for Go users, the same capability would be valuable for Node.js, Python, and .NET SDKs.


Current State

Go SDK (this PR):

  • ✅ Supports embedding CLI binary using Go's embed package
  • ✅ Automatic CLI installation when no explicit path is provided
  • ✅ Build tooling via go tool bundler
  • ✅ Eliminates need for users to install CLI separately

Node.js SDK:

  • ❌ Requires CLI installed and in PATH (or custom cliPath)
  • ❌ No bundling/embedding support

Python SDK:

  • ❌ Requires CLI installed and accessible (or custom cli_path)
  • ❌ No bundling/embedding support

.NET SDK:

  • ❌ Requires CLI installed and in PATH (or custom CliPath)
  • ❌ No bundling/embedding support

Consistency Impact

Requirements sections now differ:

Go SDK (after this PR):

## Distributing your application with an embedded GitHub Copilot CLI

The SDK supports bundling, using Go's `embed` package, the Copilot CLI binary within your application's distribution.
This allows you to bundle a specific CLI version and avoid external dependencies on the user's system.

All other SDKs:

## Requirements
- [Language runtime]
- GitHub Copilot CLI installed and in PATH (or provide custom path)

Recommendations

Option 1: Track feature parity (Recommended)

Accept this PR as-is since it's Go-specific infrastructure, but create tracking issues to implement equivalent functionality in other SDKs:

Node.js: Could use npm's optional dependencies or postinstall scripts to download platform-specific binaries
Python: Could use package data + custom installers (similar to packages like playwright or chromedriver-py)
.NET: Could use NuGet package assets and runtime-specific dependencies

Option 2: Document the difference

If bundling is intentionally Go-only (due to Go's superior embedding capabilities), explicitly document this as a Go-specific advantage in the main README to set user expectations.


Why This Matters

Distribution simplification: This feature allows Go SDK users to ship applications without external CLI dependencies, significantly improving the deployment experience. Users of other SDKs would benefit equally from this capability.

Version pinning: Embedding specific CLI versions ensures consistent behavior across deployments, which is valuable regardless of language.

Offline environments: Bundled CLIs work in airgapped/restricted environments where downloading dependencies isn't possible.


Suggested Next Steps

  1. Approve this PR - The Go implementation is well-designed and language-appropriate
  2. 📝 Create follow-up issues for bundling support in Node.js, Python, and .NET SDKs
  3. 📚 Update main README to mention bundling capability and which SDKs support it

Would you like me to help create those tracking issues or provide implementation guidance for the other languages?

AI generated by SDK Consistency Review Agent

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Cross-SDK Consistency Review: CLI Bundling Feature

I've reviewed PR #414 which adds CLI embedding/bundling functionality to the Go SDK. Here's the consistency analysis across all four SDK implementations:

Good News: Feature Parity Already Exists!

All four SDKs already have CLI bundling/embedding functionality, though implemented using different approaches appropriate to each ecosystem:

SDK Bundling Approach Status
Go (this PR) Runtime extraction from Go embed package ✅ Being added
Node.js/TypeScript npm dependency (@github/copilot package) ✅ Existing
Python Platform-specific wheels with bundled binaries ✅ Existing
.NET MSBuild targets download at build time ✅ Existing

📋 Implementation Comparison

Go SDK (this PR)

  • Build tool: go tool bundler command downloads and prepares CLI binary
  • Embedding: Uses Go's embed package to include binary in application
  • Runtime: Extracts to temp directory on first use with file locking
  • Documentation: ✅ New README section explains the workflow

Node.js/TypeScript SDK

  • Distribution: CLI is npm dependency, bundled via @github/copilot package
  • Resolution: import.meta.resolve() locates bundled CLI at runtime
  • Runtime: No extraction needed—points to JavaScript entry point
  • Documentation: ⚠️ Mentions bundled CLI but lacks detailed bundling docs

Python SDK

  • Distribution: Platform-specific wheels can include copilot/bin/ binaries
  • Resolution: _get_bundled_cli_path() checks for bundled binary
  • Runtime: Direct execution, no extraction needed
  • Documentation: ⚠️ Bundling infrastructure exists but lacks documentation

.NET SDK

  • Build tool: MSBuild .targets file downloads platform-specific binaries
  • Embedding: Copies to runtimes/{RID}/native/ in output directory
  • Runtime: GetBundledCliPath() locates platform-specific binary
  • Documentation: ⚠️ Automatic discovery not documented

🎯 Consistency Assessment

API Consistency: ✅ All SDKs follow the same priority order:

  1. Explicit user-provided path (cliPath/cli_path/CLIPath/CliPath)
  2. Environment variable (COPILOT_CLI_PATH)
  3. Bundled/embedded CLI (automatic fallback)

Naming Consistency: ✅ Follows language conventions:

  • TypeScript: cliPath (camelCase)
  • Python: cli_path (snake_case)
  • Go: CLIPath (PascalCase for exported)
  • .NET: CliPath (PascalCase)

💡 Suggestions for Improvement

While this PR maintains cross-SDK consistency, consider these non-blocking documentation improvements across all SDKs:

  1. Node.js SDK: Document the bundling mechanism more explicitly in README
  2. Python SDK: Add documentation about creating/using platform-specific wheels
  3. .NET SDK: Document the automatic bundled CLI discovery feature
  4. Go SDK: ✅ This PR already adds excellent documentation!

Conclusion

This PR maintains excellent cross-SDK consistency. The Go implementation uses idiomatic Go patterns (embed package, build tools) while achieving the same user-facing behavior as other SDKs. The feature allows users across all platforms to:

  • Bundle the CLI with their application distribution
  • Avoid requiring end-users to install the CLI separately
  • Control which CLI version their application uses

No blocking issues found. The implementation differences are appropriate ecosystem adaptations rather than inconsistencies.

AI generated by SDK Consistency Review Agent

@qmuntal qmuntal marked this pull request as ready for review February 9, 2026 15:49
@qmuntal qmuntal requested a review from a team as a code owner February 9, 2026 15:49
Copilot AI review requested due to automatic review settings February 9, 2026 15:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Go SDK support for bundling and automatically installing a GitHub Copilot CLI binary embedded into an application, so users don’t need a separate CLI installation.

Changes:

  • Introduces a small cross-platform file-locking helper (internal/flock) with tests.
  • Adds an embedded CLI installer (internal/embeddedcli) plus a small public wrapper package (embeddedcli) and hooks NewClient to prefer the embedded CLI when available.
  • Adds a go/cmd/bundler tool to download the CLI from npm, zstd-compress it, and generate Go source that embeds the artifacts; updates Go module deps and README.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
go/internal/flock/flock.go Adds Acquire API for lockfiles (currently has an open-flag bug).
go/internal/flock/flock_unix.go Unix flock implementation.
go/internal/flock/flock_windows.go Windows LockFileEx/UnlockFileEx implementation.
go/internal/flock/flock_other.go Stub implementation for unsupported platforms.
go/internal/flock/flock_test.go Concurrency + idempotent release tests for flock.
go/internal/embeddedcli/embeddedcli.go Implements lazy install + lookup of embedded CLI (hashing/dir choice need hardening).
go/internal/embeddedcli/embeddedcli_test.go Tests for setup constraints, install, version sanitization, and hash mismatch behavior.
go/embeddedcli/installer.go Public wrapper exposing embeddedcli.Setup + config type alias.
go/cmd/bundler/main.go New bundler tool to download, compress, and generate embed-ready Go code (has a panic bug + networking/resource concerns).
go/client.go Uses embedded CLI path when COPILOT_CLI_PATH is unset and default CLIPath is used (needs guard for external-server mode).
go/README.md Documents embedding flow via bundler tool.
go/go.mod Adds zstd dependency for generated code.
go/go.sum Adds checksums for new dependency.
Comments suppressed due to low confidence (3)

go/internal/embeddedcli/embeddedcli.go:130

  • After writing config.Cli to disk, the code never verifies the written file matches config.CliHash. This defeats the purpose of shipping a hash and allows silent corruption (or unexpected reader contents). Compute/compare the SHA-256 while writing (or immediately after), and return an error on mismatch (ideally via an atomic temp-file+rename install).
	f, err := os.OpenFile(finalPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return "", fmt.Errorf("creating binary file: %w", err)
	}
	_, err = io.Copy(f, config.Cli)

go/internal/embeddedcli/embeddedcli.go:123

  • If finalPath exists but has the wrong hash, installAt returns an error and gives up. A previous interrupted install (partial file) would then permanently break embedded CLI usage. Consider a safer install strategy (temp file + rename) and a recovery path for hash mismatches (e.g., delete+reinstall in a trusted per-user dir or surface a clearer remediation).
		}
		if !bytes.Equal(existingHash, config.CliHash) {
			return "", fmt.Errorf("existing binary hash mismatch")
		}
		return finalPath, nil

go/cmd/bundler/main.go:527

  • File handle may be writable as a result of data flow from a call to OpenFile and closing it may result in data loss upon failure, which is not handled explicitly.
				outFile.Close()

//
// --platform: Target platform using Go conventions (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64). Defaults to current platform.
// --output: Output directory for embedded artifacts. Defaults to the current directory.
// --cli-version: CLI version to download. If not specified, automatically detects from the copilot-sdk version in go.mod.
Copy link
Contributor

@SteveSandersonMS SteveSandersonMS Feb 9, 2026

Choose a reason for hiding this comment

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

Is there something that forces developers to realise if their local bundled version is out of date?

@github-actions
Copy link

Cross-SDK Consistency Review: Embedded CLI Support

I've reviewed PR #414 which adds embedded CLI support to the Go SDK. Here's my analysis of consistency across all four SDK implementations:

Summary

This PR maintains good cross-SDK consistency. All four SDKs now have embedded/bundled CLI support, though with different implementation approaches that are appropriate for each language's ecosystem.


Comparison of Embedded CLI Features Across SDKs

Feature Node.js Python Go (this PR) .NET
Bundling approach npm package dependencies Platform-specific wheels Manual bundler tool MSBuild targets
Distribution Automatic via npm Automatic via pip Developer runs bundler Automatic during build
Runtime installation ❌ Pre-bundled only ❌ Pre-bundled only ✅ Yes (with locking) ❌ Pre-bundled only
Hash validation ✅ SHA-256 check
Version management Single version Single version ✅ Multiple versions Single version
Concurrency control N/A N/A ✅ File locking N/A
Auto-detection ✅ Via import.meta.resolve ✅ Checks copilot/bin/ ✅ Via embeddedcli.Path() ✅ Via GetBundledCliPath()
Fallback to PATH

Key Observations

1. Different but Appropriate Approaches

Each SDK uses an approach that fits its ecosystem:

  • Node.js: Leverages npm's optional peer dependencies for platform-specific binaries
  • Python: Expects platform-specific wheels to include the binary in copilot/bin/
  • Go: Provides a manual bundler tool (go tool bundler) + runtime extraction
  • .NET: Uses MSBuild targets to download and embed during build

2. Go's Unique Features

This PR adds capabilities that other SDKs don't have:

  • Runtime installation with hash validation and file locking
  • Multi-version support (versioned binary names like copilot_0.0.403)
  • Robust concurrency handling for parallel installations

Question for consideration: Should these features be added to other SDKs for consistency? Or are they Go-specific optimizations?

3. Configuration Consistency

All SDKs follow the same resolution priority:

  1. Explicit CLI path option (cliPath/cli_path/CLIPath/CliPath)
  2. COPILOT_CLI_PATH environment variable
  3. Bundled/embedded CLI
  4. System PATH fallback

4. Documentation Consistency

The Go README now documents the bundling process similar to other SDKs. However, the workflow differs:

  • Node.js/Python/.NET: "Just install the SDK" (bundling is transparent)
  • Go: "Run go tool bundler before building" (manual step)

Recommendations

  1. ✅ No blocking issues - This PR is consistent with the overall SDK architecture

  2. 💡 Consider for future work:

    • Should hash validation be added to other SDKs for supply chain security?
    • Should multi-version support be considered for .NET/Python/Node.js?
    • Could the Go bundler step be automated (e.g., via go generate directive) to match the "zero-config" experience of other SDKs?
  3. 📝 Documentation suggestion:

    • Consider adding a comparison table (like above) to the main README showing how bundling works in each SDK

Conclusion

This PR successfully adds embedded CLI support to Go while respecting language-specific conventions. The feature is consistent at the API level (same fallback chain, same config options) while using ecosystem-appropriate implementation patterns. ✅

Great work! 🎉

AI generated by SDK Consistency Review Agent

…ithout error handling'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
}

if _, err := io.Copy(outFile, tarReader); err != nil {
outFile.Close()
@github-actions
Copy link

Cross-SDK Consistency Review

Thank you for this PR! I've reviewed the Go SDK's embedded CLI implementation for consistency with the other SDK implementations (Node.js, Python, and .NET). Here are my findings:

Summary

This PR adds embedded CLI support to the Go SDK, which is excellent for feature parity. However, there's a significant difference in user experience compared to the other three SDKs that should be considered.

Current State Across SDKs

SDK Embedded CLI Support Installation Method User Action Required
Node.js ✅ Yes Bundled in @github/copilot npm package None - automatic
Python ✅ Yes Platform-specific wheels with bundled binary None - automatic (with correct wheel)
.NET ✅ Yes MSBuild targets download CLI at build time None - automatic
Go (this PR) ✅ Yes User runs bundler tool to embed CLI Manual step required

Key Inconsistency

Other SDKs: The CLI is automatically bundled when the SDK package is installed/published. Users get the embedded CLI "out of the box" with zero configuration.

Go SDK (this PR): Users must explicitly run a bundler tool (go tool bundler) as a build step to embed the CLI binary in their application.

User Experience Impact

Node.js/Python/.NET Example:

// Just works - CLI is already bundled
client := copilot.NewClient(nil)

Go SDK (this PR) Example:

# Extra steps required before this works:
go get -tool github.com/github/copilot-sdk/go/cmd/bundler
go tool bundler

# Then in code:
embeddedcli.Setup(embeddedcli.Config{...}) // Must configure
client := copilot.NewClient(nil)

Suggestions for Better Consistency

To align with other SDKs, consider one of these approaches:

  1. Option A: Embed at SDK release time (Most consistent)

    • Bundle platform-specific CLI binaries in the Go module itself (similar to Python wheels)
    • Use Go build tags for different platforms
    • Automatically detect and use the appropriate binary at runtime
    • Pros: Zero user configuration, matches other SDKs
    • Cons: Larger module size, multiple platform binaries
  2. Option B: Automatic download at first use (Similar to .NET)

    • On first NewClient() call, download and cache the CLI binary automatically
    • Store in user cache directory (similar to the current install logic)
    • Pros: No manual bundler step, smaller module
    • Cons: Network dependency at runtime (can be mitigated with cache)
  3. Option C: Keep current approach but enhance docs (Least change)

    • If the manual bundler approach is intentional for Go's ecosystem
    • Add prominent documentation comparing Go's approach with other SDKs
    • Explain why Go requires this extra step
    • Ensure error messages guide users to run the bundler

API Consistency Notes

The PR does maintain good API-level consistency:

embeddedcli.Setup() is analogous to embedding in other SDKs
NewClient() auto-detection logic matches other SDKs
✅ Fallback behavior (embedded → PATH → error) is consistent
✅ Client options (CLIPath, CLIUrl) match other SDKs (accounting for Go naming conventions)

Recommendation

Consider whether requiring a manual bundler step aligns with the SDK's goal of feature parity and consistent user experience. If this approach is intentional for Go's ecosystem (e.g., to keep module size small), I'd suggest:

  1. Adding a clear comparison table in the Go README explaining the difference
  2. Updating the main SDK README to note this platform-specific requirement
  3. Ensuring error messages when CLI is not found suggest running the bundler

Overall, this is solid work! The implementation quality is high—this feedback is purely about user experience consistency across language SDKs.

AI generated by SDK Consistency Review Agent

1. Run `go get -tool github.com/github/copilot-sdk/go/cmd/bundler`. This is a one-time setup step per project.
2. Run `go tool bundler` in your build environment just before building your application.

That's it! When your application calls `copilot.NewClient` without a `CLIPath` nor the `COPILOT_CLI_PATH` environment variable, the SDK will automatically install the embedded CLI to a cache directory and use it for all operations.

Choose a reason for hiding this comment

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

Cross-SDK consistency note: Consider adding a comparison section here explaining why Go requires manual bundling while Node.js, Python, and .NET SDKs have the CLI automatically bundled.

For example:

> **Note**: Unlike the Node.js, Python, and .NET SDKs where the CLI is automatically bundled with the package, the Go SDK requires you to explicitly run the bundler tool. This keeps the Go module size small and gives you control over which CLI version to embed.

This would help users understand the different approach and set appropriate expectations.

AI generated by SDK Consistency Review Agent for #414


// Setup sets the embedded GitHub Copilot CLI install configuration.
// The CLI will be lazily installed when needed.
func Setup(cfg Config) {

Choose a reason for hiding this comment

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

API consistency observation: The Setup() function requiring explicit user configuration is different from other SDKs where the bundling is handled automatically at package install time.

Consider whether it would be beneficial to add a note in the godoc explaining this design choice, especially for developers familiar with the other SDK implementations. For example:

// Setup sets the embedded GitHub Copilot CLI install configuration.
// The CLI will be lazily installed when needed.
//
// Note: Unlike the Node.js, Python, and .NET SDKs where the CLI is 
// automatically bundled, the Go SDK requires explicit embedding via 
// the bundler tool to keep module size minimal.

This helps maintain consistency in understanding across the SDK family.

AI generated by SDK Consistency Review Agent for #414

// If no CLI path is provided, attempt to use the embedded CLI if available
cliPath = embeddedcli.Path()
}
if cliPath == "" {

Choose a reason for hiding this comment

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

Good consistency: This fallback logic (embedded → "copilot" in PATH) matches the behavior in other SDKs nicely.

However, when the CLI is not found, consider if the error message should mention running go tool bundler to help users who haven't embedded the CLI yet. For comparison:

  • Python SDK raises: "Copilot CLI not found. The bundled CLI binary is not available. Ensure you installed a platform-specific wheel, or provide cli_path."
  • .NET SDK throws: "Copilot CLI not found at '{searchedPath}'. Ensure the SDK NuGet package was restored correctly or provide an explicit CliPath."

A similar helpful message for Go might be: "Copilot CLI not found. Run 'go tool bundler' to embed the CLI, ensure 'copilot' is in PATH, or provide CLIPath option."

AI generated by SDK Consistency Review Agent for #414

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants