feat: hooks.d drop-in directory and config-dir env override#3496
Merged
Conversation
Load <config-dir>/hooks.d/*.yaml as standalone HooksConfig files, merged additively after settings.hooks in lexicographic order. Malformed files are skipped with a warning so a broken drop-in never breaks a run. External tools can install or uninstall hooks by writing or deleting one self-contained file. Support DOCKER_AGENT_CONFIG_DIR (legacy CAGENT_CONFIG_DIR) as a fallback when --config-dir is not passed, with flag > env > default precedence, matching the models gateway dual-env convention. Closes #3493
dgageot
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3493
Summary
Two additive changes for external tool integrations (terminal emulators, IDEs, audit/observability sidecars):
<config-dir>/hooks.d/*.yamlfiles are loaded as standaloneHooksConfigblocks and merged into the same slot as the userconfigsettings.hooksblock.DOCKER_AGENT_CONFIG_DIR(plus legacyCAGENT_CONFIG_DIRalias) is used as a fallback when--config-diris not passed.Install = write one self-contained file; uninstall = delete it. No shared
config.yamledits, no marker-block surgery, no cross-tool conflicts.Issue expectations vs implementation
<config-dir>/hooks.d/*.yaml, each file a standaloneHooksConfigpkg/config/hooks_dropin.go(LoadHookDropIns).yml, matching the repo-wide extension conventionos.ReadDir(sorted by filename), pinned by test10-,50-) control orderinghooks:block, same merge path asrunConfig.GlobalHookscmd/root/run.go:GlobalHooks = MergeHooks(userSettings.GlobalHooks(), LoadHookDropIns())settings.hooks, then drop-ins, then CLIHooksConfig.Validate(), per-file warn-and-skipDOCKER_AGENT_CONFIG_DIR+CAGENT_CONFIG_DIRalias, precedence flag > env > defaultresolveConfigDirincmd/root/root.goviacmp.OrMerge flow
All sources are additive; drop-ins ride the existing
runConfig.GlobalHooksslot, so hook semantics and existing merge precedence are unchanged. With nohooks.ddirectory and no env var set, both code paths are strict no-ops.Design notes
runConfig.GlobalHooks)" was followed: drop-ins merge into theGlobalHooksslot right aftersettings.hooks. If a different position is preferred, the merge point is a single line inrun.go.GlobalHooksis only populated on therun/execpath, exactly likesettings.hookstoday;servebehavior is unchanged.Validation
go build ./...golangci-lint run(full repo)go run ./lint .(custom cops)go teston touched packages (pkg/config,cmd/root,pkg/teamloader,pkg/userconfig,pkg/paths)pkg/config,TestURLSource_Read_RejectsLocalAddresses, reproduced on cleanmain)New tests cover: lexicographic order, additive merge across files and events, warn-and-skip (broken YAML, unknown key, invalid hook type), non-YAML / subdirectory / empty-file handling,
<config-dir>/hooks.dwiring, and env-var precedence.