Skip to content

feat: hooks.d drop-in directory and config-dir env override#3496

Merged
dgageot merged 1 commit into
mainfrom
feat/hooks-dropin-and-config-dir-env
Jul 7, 2026
Merged

feat: hooks.d drop-in directory and config-dir env override#3496
dgageot merged 1 commit into
mainfrom
feat/hooks-dropin-and-config-dir-env

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Closes #3493

Summary

Two additive changes for external tool integrations (terminal emulators, IDEs, audit/observability sidecars):

  1. Hooks drop-in directory: <config-dir>/hooks.d/*.yaml files are loaded as standalone HooksConfig blocks and merged into the same slot as the userconfig settings.hooks block.
  2. Config-dir env override: DOCKER_AGENT_CONFIG_DIR (plus legacy CAGENT_CONFIG_DIR alias) is used as a fallback when --config-dir is not passed.

Install = write one self-contained file; uninstall = delete it. No shared config.yaml edits, no marker-block surgery, no cross-tool conflicts.

Issue expectations vs implementation

Issue expectation Implementation Notes
Load <config-dir>/hooks.d/*.yaml, each file a standalone HooksConfig pkg/config/hooks_dropin.go (LoadHookDropIns) also accepts .yml, matching the repo-wide extension convention
Lexicographic order os.ReadDir (sorted by filename), pinned by test numeric prefixes (10-, 50-) control ordering
Additive merge after userconfig hooks: block, same merge path as runConfig.GlobalHooks cmd/root/run.go: GlobalHooks = MergeHooks(userSettings.GlobalHooks(), LoadHookDropIns()) effective per-event order: agent-config, then settings.hooks, then drop-ins, then CLI
Malformed files: log a warning and skip strict YAML parse + HooksConfig.Validate(), per-file warn-and-skip a typo'd event name surfaces in logs instead of being silently ignored
DOCKER_AGENT_CONFIG_DIR + CAGENT_CONFIG_DIR alias, precedence flag > env > default resolveConfigDir in cmd/root/root.go via cmp.Or same dual-env convention as the models gateway
Non-goals: no agent-schema change, no hook semantics change no schema file or frozen config version touched teamloader untouched

Merge flow

agent-config hooks     (agent YAML)
        |
settings.hooks         (~/.config/cagent/config.yaml)
        |
hooks.d drop-ins       (<config-dir>/hooks.d/*.yaml, lexicographic)
        |
CLI hooks              (--hook-* flags)

All sources are additive; drop-ins ride the existing runConfig.GlobalHooks slot, so hook semantics and existing merge precedence are unchanged. With no hooks.d directory and no env var set, both code paths are strict no-ops.

Design notes

  • The issue phrase "before agent-config and CLI hooks" conflicts with the runtime's actual ordering (agent-config hooks always run first). The decisive parenthetical "(same merge path as runConfig.GlobalHooks)" was followed: drop-ins merge into the GlobalHooks slot right after settings.hooks. If a different position is preferred, the merge point is a single line in run.go.
  • Strict parsing (unknown keys rejected) was chosen so a typo'd event name produces a logged warning instead of a silent no-op. If lenient parsing seems more appropriate, it can always be changed.
  • GlobalHooks is only populated on the run/exec path, exactly like settings.hooks today; serve behavior is unchanged.

Validation

Check Result
go build ./... pass
golangci-lint run (full repo) 0 issues
go run ./lint . (custom cops) no offenses
go test on touched packages (pkg/config, cmd/root, pkg/teamloader, pkg/userconfig, pkg/paths) pass (one pre-existing network-dependent failure in pkg/config, TestURLSource_Read_RejectsLocalAddresses, reproduced on clean main)
End-to-end smoke with the real binary drop-in hook fired; broken drop-in skipped without breaking the run; first-run marker landed in the env-specified dir; flag wins over env

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.d wiring, and env-var precedence.

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
@Sayt-0
Sayt-0 requested a review from a team as a code owner July 7, 2026 08:52
@dgageot
dgageot merged commit 065cf54 into main Jul 7, 2026
18 checks passed
@dgageot
dgageot deleted the feat/hooks-dropin-and-config-dir-env branch July 7, 2026 08:57
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.

[Proposal] Hooks drop-in directory (hooks.d) + config-dir env override for external tool integrations

2 participants