feat: Add support for Go 1.23 `godebug` directive
What type of PR is this?
Feature
What package or component does this PR mostly affect?
internal/bzlmod
What does this PR do? Why is it needed?
This PR adds support for parsing the godebug directive introduced in Go 1.23. The directive allows setting GODEBUG environment variables at the module level, controlling runtime behavior without requiring environment variables to be set externally.
Currently, Gazelle fails when parsing go.mod files containing the godebug directive with:
unexpected token 'godebug' at start of line
This implementation:
- Extends the go.mod parser to recognize
godebugas a valid directive - Supports both single-line (
godebug key=value) and block syntax - Propagates godebug settings through the go_env mechanism as GODEBUG environment variable
- Handles godebug in go.work files (which override module-level settings)
- Maintains backward compatibility for go.mod files without godebug
Which issues(s) does this PR fix?
Fixes #2137
Other notes for review
- The implementation follows the existing pattern for other directives like
toolandrequire - godebug values are merged into go_env, allowing them to affect Go toolchain behavior
- Comprehensive tests added for both go.mod and go.work parsing
- All existing tests pass without modification
This PR was co-authored with AI assistance.
From the docs:
To override these defaults, starting in Go 1.23, the work module’s go.mod or the workspace’s go.work can list one or more
godebuglines:godebug ( default=go1.21 panicnil=1 asynctimerchan=0 )The special key default indicates a Go version to take unspecified settings from. This allows setting the GODEBUG defaults separately from the Go language version in the module. In this example, the program is asking for Go 1.21 semantics and then asking for the old pre-Go 1.21 panic(nil) behavior and the new Go 1.23 asynctimerchan=0 behavior.
Only the work module’s go.mod is consulted for godebug directives. Any directives in required dependency modules are ignored. It is an error to list a godebug with an unrecognized setting. (Toolchains older than Go 1.23 reject all godebug lines, since they do not understand godebug at all.)
The defaults from the go and godebug lines apply to all main packages that are built. For more fine-grained control, starting in Go 1.21, a main package’s source files can include one or more //go:debug directives at the top of the file (preceding the package statement). The godebug lines in the previous example would be written:
//go:debug default=go1.21 //go:debug panicnil=1 //go:debug asynctimerchan=0
Sorry for the spam and please let me know if I should refrain from sharing this in a PR comment, but for this time:
Disclosing the prompt in case it's helpful to anyone.
This was the initial prompt (co-authored with OpenAI o3):
<role>
Senior Bazel & Go build-tooling engineer.
</role>
<objective>
Extend bazel-gazelle so it can parse the new <code>godebug</code> directive introduced in Go 1.23 <code>go.mod</code> files,
and correctly propagate any effects it has on dependency resolution or module behavior.
</objective>
<resources>
<link>https://go.dev/doc/godebug</link>
<link>https://tip.golang.org/doc/go1.23</link>
<link>https://go.dev/ref/mod</link>
<link>https://github.com/bazel-contrib/bazel-gazelle/issues/2137</link>
<repro>github.com/albertocavalcante/repros@main/2025-07-08-bazel-gazelle</repro>
</resources>
<context>
Gazelle currently errors on any <code>go.mod</code> containing a <code>godebug</code> directive.
The parser treats it as an invalid top-level token.
</context>
<stacktrace><![CDATA[
ERROR: /private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl:215:21: Traceback (most recent call last):
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_deps.bzl", line 428, column 95, in go_deps_impl
module_path, module_tags_from_go_mod, go_mod_replace_map, tools = deps_from_go_mod(module_ctx, from_file_tag.go_mod)
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl", line 171, column 26, in deps_from_go_mod
go_mod = parse_go_mod(go_mod_content, go_mod_path)
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl", line 215, column 21, in parse_go_mod
fail("{}:{}: unexpected token '{}' at start of line".format(path, line_no, tokens[0]))
Error in fail: /Users/user/dev/workspace/repros/2025-07-08-bazel-gazelle/go.mod:5: unexpected token 'godebug' at start of line
]]></stacktrace>
<tasks>
1. Analyse <file>internal/bzlmod/go_mod.bzl</file> parsing logic.
2. Extend grammar to accept the optional <code>godebug</code> directive (key-value syntax).
3. Ensure that Gazelle not only accepts <code>godebug</code>, but that its **effects on module resolution, toolchain behavior, or dependency semantics** are **fully captured and preserved** in any downstream logic. This is a mandatory requirement.
4. Maintain backward compatibility: files without <code>godebug</code> must remain unaffected.
5. Add test coverage:
• Unit test with minimal <code>go.mod</code> including <code>godebug</code>.
• Regression tests for existing inputs.
6. Ensure full CI pass via <code>bazel test //...</code>.
7. Document the change if any behavior is user-visible (README, CHANGELOG).
8. Submit PR on branch <code>feat/godebug-support</code>, referencing GitHub issue #2137.
</tasks>
<acceptance-criteria>
• <code>bazel run //:gazelle</code> completes successfully on the provided repro.
• All new and existing tests pass locally and in CI.
• <code>buildifier -lint=fix</code> reports no new issues.
• If <code>godebug</code> changes any downstream behavior (e.g., build tags, tool args, etc.), those changes must be **reflected in Gazelle outputs**.
• PR must clearly document root cause, parser extension, tests, and behavioral impact.
</acceptance-criteria>
<guidelines>
• Follow idiomatic Starlark (2-space indent, snake_case).
• Prefer reusable parsing logic to accommodate future directives.
• Keep parsing deterministic and output stable.
• Target Go ≥ 1.23, as this directive is not recognized earlier.
</guidelines>
Then, after a few iterations with Claude Code in Plan mode, this was the approved plan:
User approved Claude's plan:
⎿ Revised Plan: Add godebug Directive Support to Bazel Gazelle
Overview
The godebug directive in go.mod files should be parsed and propagated through the go_env mechanism, not treated as a dependency. This
aligns with how Go uses GODEBUG settings to control runtime behavior.
Current Understanding
1. go_env flow: Users can set environment variables via go_deps.config(go_env = {...}) in MODULE.bazel
2. Propagation: go_env is passed to go_repository_cache and used when running Go commands
3. godebug semantics: The directive sets GODEBUG values at the module level, affecting compiler/runtime behavior
Implementation Steps
1. Extend go.mod Parser (internal/bzlmod/go_mod.bzl)
- Add "godebug" to valid directives list (line 214)
- Add godebug parsing in _parse_directive function
- Support both single-line and block syntax:
- Single: godebug key=value
- Block: godebug ( key1=value1 key2=value2 )
- Store parsed values in state["godebug"] as a dict
2. Update Parser Return Structure
- Add godebug field to the struct returned by parse_go_mod
- Return empty dict if no godebug directives found
3. Propagate godebug to go_env
- In go_deps.bzl, extract godebug from parsed go.mod
- When creating _go_repository_config, merge godebug values into go_env
- Format: Convert godebug key=value to GODEBUG=key=value,key2=value2
- Handle merging with existing GODEBUG in go_env (concatenate with commas)
4. Add Tests
- Test go.mod parsing with godebug directive (both formats)
- Test GODEBUG environment variable generation
- Test merging with existing GODEBUG values
- Ensure backward compatibility
5. Handle go.work Files
- Apply same parsing logic to go.work files
- Note: Per Go spec, godebug in go.work overrides module-level settings
Key Changes
1. go_mod.bzl:
- Add godebug to valid directives
- Parse key=value pairs
- Return godebug dict in parse result
2. go_deps.bzl:
- Extract godebug from go.mod parse result
- Convert to GODEBUG environment variable
- Pass through go_env to go_repository_config
3. Tests:
- Add test cases in go_mod_test.bzl
- Verify GODEBUG propagation
Minimal Scope
- Only parse and propagate godebug values
- No validation of godebug keys (Go will handle that)
- No changes to go_repository or other downstream components
- GODEBUG will naturally flow through existing go_env mechanism
This approach ensures godebug directives are properly parsed and affect the Go toolchain environment without treating them as
dependencies.
This should live in rules_go's go_sdk extension, not in gazelle's go_deps. Gazelle's go env specifically doesn't influence the build time environment and it's not a good fit for what is arguably an SDK configuration.
It would be a very welcome addition to rules_go though - maybe you just need to lightly tweak the prompt? :-)
@fmeum Thanks, Fabian! :)
However, from the stacktrace (https://github.com/bazel-contrib/bazel-gazelle/issues/2137#issue-3214102969), my understanding is that gazelle also requires a patch, right? Currently, it breaks parsing go.mod. (Even if it doesn't affect the build environment and therefore the scope of the PR must be reduced).
Please correct me if I'm wrong.
Sharing also here for convenience:
ERROR: /private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl:215:21: Traceback (most recent call last):
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_deps.bzl", line 428, column 95, in *go*deps_impl
module_path, module_tags_from_go_mod, go_mod_replace_map, tools = deps_from_go_mod(module_ctx, from_file_tag.go_mod)
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl", line 171, column 26, in deps_from_go_mod
go_mod = parse_go_mod(go_mod_content, go_mod_path)
File "/private/var/tmp/_bazel_user/b657a3728cd030fff5d5deff34f01ea1/external/gazelle+/internal/bzlmod/go_mod.bzl", line 215, column 21, in parse_go_mod
fail("{}:{}: unexpected token '{}' at start of line".format(path, line_no, tokens[0]))
Error in fail: /Users/user/dev/workspace/repros/2025-07-08-bazel-gazelle/go.mod:5: unexpected token 'godebug' at start of line
If the above is true, I will focus this PR solely on not breaking the parsing of go.mod if godebug is present and move the actual merge and apply to GODEBUG/go_env to rules_go.
Yes, Gazelle has to be changed to ignore these lines. Your plan sounds good!