feat(plugin): add prompt-routing hooks and doctor/setup commands#128
Open
simonfaltum wants to merge 10 commits into
Open
feat(plugin): add prompt-routing hooks and doctor/setup commands#128simonfaltum wants to merge 10 commits into
simonfaltum wants to merge 10 commits into
Conversation
Add a Claude Code hook and command layer on top of the existing skills, keeping the plugin CLI-first (no MCP). Hooks (in hooks/, auto-loaded via hooks/hooks.json; both fail open, stdlib only): - databricks-router.py (UserPromptSubmit): a fast keyword regex that routes Databricks-related prompts into databricks-core plus the matching product skill. Modeled on Snowflake Cortex Code. No permission gating, no cost warnings. - databricks-context.py (SessionStart): primes the routing rule and reports CLI version and configured profile names (local only, no network, no token values). Commands (in commands/): /databricks:doctor (read-only health check) and /databricks:setup (auth onboarding). Product workflows stay in the skills, so no command shadows a skill of the same name. Tooling: scripts/skills.py validate now checks the components (valid hooks.json referencing scripts that exist, every command has a description, and plugin.json does not double-declare the auto-loaded hooks file). CI runs the router unit test. README, CONTRIBUTING, and CLAUDE.md document the new surface. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
Remove the competitor attribution from the prompt-router docstrings, READMEs, and PR-facing docs, and drop snowflake/cortex from the router's SUPPRESS keyword list and tests. The router is just a keyword matcher, so no attribution is needed. Other competitor suppressors (bigquery, redshift, synapse) stay, and routing behavior for Databricks prompts is unchanged (8/8 tests pass). Co-authored-by: Isaac
… router + validator tuning) - hooks.json: append `|| true` so the hook is fully fail-open even when neither python3 nor python can run the script. - databricks-context.py: only read the config file when it is a regular file under a 1MB cap, so a FIFO/device/huge file pointed at by DATABRICKS_CONFIG_FILE can never hang or do unbounded work at session start; sanitize injected profile names and DATABRICKS_CONFIG_PROFILE (strip control chars/newlines, cap length) so config-derived strings can't inject context. - databricks-router.py: tune keyword tiers — drop `photon` and move `genie` out of STRONG into AMBIGUOUS (common words were over-routing), add `\bdabs?\b` so DAB/DABs prompts route, and trim the suppress list. - scripts/skills.py: require plugin.json to declare `commands` when commands/ exists (mirror of the no-`hooks`-declaration rule). Addresses the cursor-agent review of PR #128. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
The SessionStart hook can't know the latest CLI version offline, and any pinned floor just goes stale (0.292.0 already had, with the CLI now at 1.2.x). So stop gating on a version: the context hook reports the detected version only, and the commands + hooks README no longer cite a specific minimum. CLI currency is left to the CLI's own update notice; the databricks-core skill stays the single source of truth for any hard minimum. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
…e config read From a skill-review pass over the PR: - Replace every em-dash in the new hooks, commands, validator error messages, and the README/CONTRIBUTING/CLAUDE docs with commas/periods/colons (house style). Two of these shipped into runtime: the router's injected instruction and the SessionStart banner. - Narrow the router's `\bdabs?\b` to `\bdabs\b` so a bare "dab" no longer routes. - Document why the SessionStart hook parses ~/.databrickscfg directly instead of calling `databricks auth profiles` (must stay offline and fast). Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
- Quote command frontmatter descriptions (strict YAML rejects unquoted colons) and make the validator flag unquoted ':' in command descriptions - Blank code-hosting URLs in the router before matching, so github.com/databricks/... alone does not route; URLs whose hostname contains databricks (workspace, docs) still do - /databricks:setup: hand interactive auth commands to the user's own terminal instead of running them without a TTY - Context hook: use the sanitized config basename in the no-profiles message, matching the profiles-found branch - skills.py validate: clean error on malformed plugin.json or marketplace.json, and catch list-form hooks declarations of the auto-loaded hooks file - Add hooks/databricks_context_test.py and run it in CI Co-authored-by: Isaac
Optimizations: - Router injects the full routing instruction once per session (marker file keyed on session_id); later Databricks prompts get a one-line reminder instead of the full block - SessionStart context primer uses matcher startup|clear|compact so it no longer re-injects the banner on resume Improvements: - New PostToolUse hook (databricks-auth-helper.py, matcher Bash): when a databricks command fails with an auth-shaped error, injects one line suggesting /databricks:doctor or databricks auth login; phrase-based matching so bare status codes in data never trip it - Router keywords: delta sharing and cloudFiles (strong), sql warehouse and auto loader (ambiguous), snowflake (suppress), and a Genie line in the routing instruction - Context banner surfaces [__settings__].default_profile when set - Doctor identity step uses databricks auth describe (credential source) with current-user as fallback, never --sensitive Tests for all of the above; CI consolidates the hook test runs into one step. Docs updated (README, CONTRIBUTING, CLAUDE.md, hooks/README). Co-authored-by: Isaac
The auth helper matched \bdatabricks\b anywhere in the command string, so gh/git/curl commands referencing the databricks GitHub org or docs tripped the hint whenever their output quoted an auth-failure phrase (observed live on 'gh pr view --repo databricks/databricks-agent-skills', whose PR body describes this very hook). Detection now splits the command into shell segments and requires the databricks executable (optionally path-prefixed, behind env assignments or sudo/env/xargs-style wrappers) to lead a segment. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
7 tasks
Removing a shipped plugin's entry from marketplace.json does not just stop updates. Claude Code re-resolves installed plugins against the marketplace catalog at load time, so existing installs immediately fail to load and lose all skills, hooks, and commands. Verified empirically in an isolated CLAUDE_CONFIG_DIR: after removing the entry and refreshing, plugin list shows 'failed to load: Plugin databricks not found in marketplace', and recovery requires manual uninstall + reinstall. Relevant for the planned official marketplace listing: that is an additive channel, never a replacement for the entry here. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
…er docs Review feedback (Renaud): this repo is public, and framing the suppression list as competitor detection invites bad optics for zero functional benefit. Comments, README, and test names now say alternative platform; the routing behavior is unchanged. Co-authored-by: Isaac Signed-off-by: simon <simon.faltum@databricks.com>
parthban-db
approved these changes
Jun 10, 2026
Comment on lines
+25
to
+31
| 4. **Workspace reach**: `databricks catalogs list --profile <profile>`. | ||
| Confirms API reachability and Unity Catalog access. | ||
| 5. **Compute**: `databricks warehouses list` and `databricks clusters list` for | ||
| the profile. Note what's running. | ||
| 6. **Recent job failures**: list recent job runs (e.g. | ||
| `databricks jobs list-runs --limit 20 --profile <profile>`) and surface any | ||
| recent failures. |
There was a problem hiding this comment.
Why do we have these checks? What value does it provide? Because if we want to see whether the profile is valid, we can simply test a single API.
| @@ -0,0 +1,40 @@ | |||
| --- | |||
| description: "Set up Databricks CLI auth: install check, then an OAuth / PAT / service-principal profile, then verify." | |||
| argument-hint: "[workspace-url]" | |||
Comment on lines
+115
to
+120
| print(json.dumps({ | ||
| "hookSpecificOutput": { | ||
| "hookEventName": "PostToolUse", | ||
| "additionalContext": result, | ||
| } | ||
| })) |
There was a problem hiding this comment.
Not sure, but can it error out? To be safe, should we cover the entire main block with a try cache?
Comment on lines
+33
to
+38
|
|
||
| - name: Test plugin hooks | ||
| run: | | ||
| python3 hooks/databricks_router_test.py | ||
| python3 hooks/databricks_context_test.py | ||
| python3 hooks/databricks_auth_helper_test.py |
There was a problem hiding this comment.
Can we have the test in a separate test directory? and run the complete suite with a single command?
dustinvannoy-db
approved these changes
Jun 10, 2026
dustinvannoy-db
left a comment
Collaborator
There was a problem hiding this comment.
One comment to consider and messaged about a design decision. Nothing blocking this PR from my perspective.
| Precision is tuned to avoid over-routing: | ||
|
|
||
| - **STRONG** terms (`databricks`, `unity catalog`, `lakeflow`, `dbfs`, | ||
| `databricks.yml`, `delta live tables`, `genie`, ...) always route, even |
Collaborator
There was a problem hiding this comment.
Should change delta live tables to declarative pipelines
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.
Why
The plugin shipped skills only. The skills carry the Databricks judgment, but nothing made sure a Databricks-related prompt actually reached them, and there were no entry points for common setup or health-check tasks. This adds a thin hook and command layer so Databricks work routes into the skills automatically, staying CLI-first (no MCP).
What the hooks do (plain language)
The hooks make sure Databricks work goes through the skills instead of Claude improvising:
/clear, and after compaction, but not on resume where the banner is already in context) and injects the routing reminder plus a one-line CLI/profile status.databrickscommand results; when one fails with an auth-shaped error (expired/invalid token, missing credentials, OAuth refresh failure), it adds one line suggesting/databricks:doctorordatabricks auth loginbefore any retry.All three only inject context that Claude then acts on (technically
additionalContext, a just-in-time nudge, not a forced skill load). They never gate, block, or change your commands, there are no permission prompts or cost warnings, and they fail open (any error means no output, never a blocked prompt or tool call).Changes
Before: skills only. Now: skills + three hooks + two commands.
Hooks (
hooks/, auto-loaded viahooks/hooks.json, all fail open, stdlib only):databricks-router.py(UserPromptSubmit): a sub-50ms keyword regex over the prompt. On a Databricks match it injects context steering Claude to loaddatabricks-coreplus the matching product skill; the full instruction is injected once per session (marker file keyed onsession_id), then a one-line reminder. Unrelated prompts are left untouched. Code-hosting URLs (github.com/databricks/...) do not count as Databricks intent; URLs whose hostname containsdatabricks(workspace, docs) do.databricks-context.py(SessionStart, matcherstartup|clear|compact): injects the routing rule plus CLI version, configured profile names, and any[__settings__].default_profile(read locally, no network call, no token values).databricks-auth-helper.py(PostToolUse, matcherBash): phrase-based auth-failure detection ondatabrickscommand output (cannot configure default credentials,invalid_grant,401 unauthorized, invalid/expired token); injects a one-line fix suggestion. Bare status codes in ordinary data never trip it, and only commands that actually invoke thedatabricksexecutable count (a repo path or URL containingdatabricks, likegh pr view --repo databricks/cli, does not).Commands (
commands/):/databricks:doctor: read-only health check (CLI, auth method viaauth describe, reachability, compute, recent run failures)./databricks:setup: auth onboarding (OAuth / PAT / service principal). Interactive auth commands (PAT paste, browserless OAuth) are handed to the user's own terminal instead of being run without a TTY.Product workflows stay in the skills, so no command shadows a skill of the same name.
plugin.jsondeclarescommands;hooks/hooks.jsonis auto-loaded by Claude Code and intentionally not declared (declaring the standard path double-loads it and fails the plugin).Tooling:
scripts/skills.py validatenow validates the components (hooks.json is valid JSON referencing scripts that exist, every command has a description without unquoted-colon YAML pitfalls, and plugin.json does not re-declare the auto-loaded hooks file, string or list form). CI runs all hook unit test files. README, CONTRIBUTING, and CLAUDE.md document the new surface.Test plan
python3 hooks/databricks_router_test.py(strong / ambiguous / suppressed routing, URL handling, once-per-session memo, payload shapes)python3 hooks/databricks_context_test.py(config parsing, default_profile, sanitization, no-CLI and no-profiles banners)python3 hooks/databricks_auth_helper_test.py(auth-failure phrases, executable-token command detection, non-databricks/non-Bash/bare-status negatives)python3 scripts/skills.py validate/reload-pluginsloads the hooks, the router injects context live, both commands registersafe_load)Distribution & releases
How this reaches users, and how it relates to releases:
databricks/databricks-agent-skills,source: "./"): tracksmain, but clients only pick up a change once theversionfield in.claude-plugin/plugin.jsonis bumped, which happens only via the release workflow (bump_version.pycommits the bump tomainand tagsvX.Y.Z). This PR intentionally does not bumpversion(the release workflow owns that), so these hooks/commands land onmainat merge but reach marketplace users only after the next release is cut, not on merge.databricks aitools install(CLI path): resolves viacli-compat.jsonindatabricks/cliand currently packages skills only. Shippinghooks/+commands/through this path is separate CLI-side follow-up work.Ship path: merge this, then run the release workflow for a new
vX.Y.Z(bumpsversion+ tags), and marketplace clients get it onmarketplace update.This pull request and its description were written by Isaac.