Skip to content

Hook config with a mis-cased event key (e.g. PreToolUse) is silently dropped — only a debug-level log, no visible warning #3872

@ken-jo

Description

@ken-jo

Describe the bug

If a hook config (~/.copilot/config.json hooks, .github/hooks/*.json, or a plugin's hooks.json) has an event key that isn't one of the recognized camelCase names, the CLI silently drops it. The hook never registers and never fires, and at normal log levels nothing tells you why.

The easiest way to hit this is casing: the docs use camelCase (preToolUse, postToolUse, …), but it's easy to write PreToolUse (PascalCase, like Claude Code's hook events) or make a typo. When that happens the config still loads "successfully" and the bad key is quietly ignored.

In the bundled app.js (1.0.63) the loader validates keys against a hardcoded set:

Wir = new Set(["sessionStart","sessionEnd","userPromptSubmitted","preToolUse",
               "postToolUse","errorOccurred","agentStop","subagentStop","preCompact"])

The hooks schema is .passthrough(), so an unknown key passes validation instead of erroring — and the only place it's reported is gated behind debug mode:

if (e?.debug) {
  const l = Object.keys(config.hooks).filter(c => !Wir.has(c));
  if (l.length > 0) e.debug(`Ignoring unknown hook event(s) in ${file}: ${l.join(", ")}`);
}

Worth noting: the loader's else branch already calls e.error(...) for schema-invalid configs. Unknown keys are the one config mistake that escapes that visible path, purely because .passthrough() routes them around it. The dispatcher then only ever reads the exact camelCase keys, so a mis-cased key never fires.

I ran into this while building agent-connector — an SDK that deploys one MCP server + lifecycle hooks across many agent CLIs from a single definition — and spent a while puzzled that my Copilot hooks never ran, with nothing in the normal output to explain it. The fix on my end was a one-character casing change.

Affected version

GitHub Copilot CLI 1.0.63

Steps to reproduce the behavior

  1. Create .github/hooks/test.json with a PascalCase event key:
    { "version": 1, "hooks": { "PreToolUse": [ { "type": "command", "bash": "echo fired >> /tmp/hook.log" } ] } }
  2. Start a session and run a bash command.
  3. The hook never fires, /tmp/hook.log is never written, and normal output says nothing. The only trace is at --log-level debug: Ignoring unknown hook event(s) in <file>: PreToolUse.
  4. Change the key to preToolUse — it works.

Expected behavior

An unrecognized hook event key should surface a visible warning at normal log level (naming the bad key and file, ideally hinting the closest valid name), the same way an otherwise-invalid config already does — not just a debug-only line. The unknown-key diff is already computed for the debug log, so it's mostly a matter of routing it to a visible warning. A silently non-firing hook is hard to diagnose, especially since there's currently no way to list registered hooks to confirm what loaded (#3871).

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:configurationConfig files, instruction files, settings, and environment variablesarea:pluginsPlugin system, marketplace, hooks, skills, extensions, and custom agents

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions