From 036f001c9e3e029c090e293c4d2bdaaa2a9a577b Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Mon, 4 May 2026 15:59:35 +0200 Subject: [PATCH 1/2] feat(appkit): agent-browser skill --- manifest.json | 19 +- skills/databricks-apps/SKILL.md | 1 + .../references/appkit/agent-browser.md | 162 ++++++++++++++++++ .../references/appkit/overview.md | 1 + 4 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 skills/databricks-apps/references/appkit/agent-browser.md diff --git a/manifest.json b/manifest.json index 54ec72f..77a41fe 100644 --- a/manifest.json +++ b/manifest.json @@ -1,17 +1,18 @@ { "version": "2", - "updated_at": "2026-04-30T11:02:41Z", + "updated_at": "2026-04-30T12:02:18Z", "skills": { "databricks-apps": { "version": "0.1.1", "description": "Databricks Apps development and deployment (evaluates analytics vs synced tables data access)", "experimental": false, - "updated_at": "2026-04-30T11:00:26Z", + "updated_at": "2026-04-30T12:02:15Z", "files": [ "SKILL.md", "agents/openai.yaml", "assets/databricks.png", "assets/databricks.svg", + "references/appkit/agent-browser.md", "references/appkit/appkit-sdk.md", "references/appkit/files.md", "references/appkit/frontend.md", @@ -33,7 +34,7 @@ "version": "0.1.0", "description": "Core Databricks skill for CLI, auth, and data exploration", "experimental": false, - "updated_at": "2026-04-23T13:47:44Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -48,7 +49,7 @@ "version": "0.0.0", "description": "Declarative Automation Bundles (DABs) for deploying and managing Databricks resources", "experimental": false, - "updated_at": "2026-04-23T13:47:44Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -66,7 +67,7 @@ "version": "0.1.0", "description": "Databricks Jobs orchestration and scheduling", "experimental": false, - "updated_at": "2026-04-23T13:47:44Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -78,7 +79,7 @@ "version": "0.1.0", "description": "Databricks Lakebase Postgres: projects, scaling, connectivity, synced tables, and Data API", "experimental": false, - "updated_at": "2026-04-30T11:02:37Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -93,7 +94,7 @@ "version": "0.1.0", "description": "Databricks Model Serving endpoint management", "experimental": false, - "updated_at": "2026-04-23T13:47:44Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -105,7 +106,7 @@ "version": "0.1.0", "description": "Databricks Pipelines (DLT) for ETL and streaming", "experimental": false, - "updated_at": "2026-04-23T13:47:44Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", @@ -152,7 +153,7 @@ "version": "0.1.0", "description": "Migrate Databricks workloads from classic compute to serverless compute, including compatibility checks and concrete fixes", "experimental": false, - "updated_at": "2026-04-24T15:10:23Z", + "updated_at": "2026-04-30T11:30:47Z", "files": [ "SKILL.md", "agents/openai.yaml", diff --git a/skills/databricks-apps/SKILL.md b/skills/databricks-apps/SKILL.md index 515fa9c..8bc8729 100644 --- a/skills/databricks-apps/SKILL.md +++ b/skills/databricks-apps/SKILL.md @@ -28,6 +28,7 @@ Build apps that deploy to Databricks Apps platform. | Typed data contracts (proto-first design) | [Proto-First Guide](references/appkit/proto-first.md) and [Plugin Contracts](references/appkit/proto-contracts.md) | | Managing files in UC Volumes | [Files Guide](references/appkit/files.md) | | Triggering / monitoring Lakeflow Jobs from the app | [Jobs Guide](references/appkit/jobs.md) | +| Driving the running app in a browser (visual checks, screenshots, agent-driven UI smoke) | [agent-browser Guide](references/appkit/agent-browser.md) | | Platform rules (permissions, deployment, limits) | [Platform Guide](references/platform-guide.md) — READ for ALL apps including AppKit | | Non-AppKit app (Streamlit, FastAPI, Flask, Gradio, Next.js, etc.) | [Other Frameworks](references/other-frameworks.md) | diff --git a/skills/databricks-apps/references/appkit/agent-browser.md b/skills/databricks-apps/references/appkit/agent-browser.md new file mode 100644 index 0000000..5cdf889 --- /dev/null +++ b/skills/databricks-apps/references/appkit/agent-browser.md @@ -0,0 +1,162 @@ +# agent-browser: Render a Running App for Visual Inspection + +Use this when you (or an agent) need to **load a Databricks app in a browser and interact with it programmatically** — to verify a UI change, capture a screenshot, or drive a smoke flow. + +There are two modes. Pick the right one before scripting: + +| | **Local app** | **Deployed app** | +|---|---|---| +| What you're driving | `databricks apps run-local` on `localhost:` | A real `https://..databricksapps.com` URL | +| Auth | None — proxy injects `X-Forwarded-*` identity | Real OAuth/SSO required | +| Default command | `agent-browser open http://localhost:8001` | `agent-browser --headed --profile open ` (first run); plain `agent-browser open ` after | +| When | Iterating on the app from a checkout | Verifying what's actually deployed in staging/prod | + +The browser daemon persists across commands either way; the only thing that changes is how the session is authenticated. + +## When NOT to use this + +- **Iterating on AppKit UI code** (HMR, fast reload) — use `npm run dev` from the app directory instead. `run-local` runs the app the way the platform runs it, not the way Vite does. +- **Verifying real per-user OBO behavior on local** — the injected identity is the CLI-authenticated user, not a per-session user. Fine for visualization, not for OBO permission testing. For OBO, drive the deployed app instead. +- **Non-visual smoke tests** — for AppKit apps the scaffold ships `tests/smoke.spec.ts` (Playwright); see [testing.md](../testing.md). + +## Pre-flight checks + +Run these before anything else. If any fail, surface the failing check and stop — don't try to fix forward. + +1. **`databricks` on PATH and authenticated** — `databricks current-user me --profile ` should print a user. If not, run `databricks auth login --host ` and stop. +2. **`agent-browser` on PATH** — `command -v agent-browser`. If missing, install per the team's instructions and stop. +3. **(Local mode only) App spec present** — `app.yaml` (AppKit scaffolds) or `app.yml` in the working directory, OR pass `--entry-point `. If neither, ask the user which directory the app lives in. +4. **(Local mode only) Proxy port free** — default is 8001. If `lsof -i :8001` shows anything, pick a free port and pass `--port `. Remember that port when invoking agent-browser. + +## Driving a local app (`run-local` proxy) + +From the app directory: + +```bash +# First run, when deps aren't installed (requires `uv`; for Node-only apps `npm install` is enough — skip the flag): +databricks apps run-local --prepare-environment --profile + +# Subsequent runs: +databricks apps run-local --profile + +# AppKit apps use app.yaml (not app.yml): +databricks apps run-local --entry-point app.yaml --profile + +# Custom ports: +databricks apps run-local --port 8001 --app-port 8000 --entry-point app.yaml --profile +``` + +`run-local` runs in the foreground and prints `To access your app go to http://localhost:` once the proxy is listening. Run it in the background (or a separate shell) so you can drive `agent-browser` against it. + +If `app.yaml` declares env vars with `valueFrom` (referencing bundle resources like SQL warehouse IDs or Genie space IDs), `run-local` will exit with `... defined in app.yaml with valueFrom property and can't be resolved locally`. Pass each as `--env KEY=value`; values come from `databricks.yml` `targets..variables`. + +Wait until the proxy is reachable before scripting against it: + +```bash +until curl -fsS "http://localhost:8001/" -o /dev/null; do sleep 1; done +``` + +Then drive it with `agent-browser open http://localhost:8001` (see "Driving with agent-browser" below). + +## Driving a deployed app (real URL, real auth) + +For URLs like `https://..databricksapps.com`, `run-local`'s header-injection trick doesn't apply — the platform demands real OAuth/SSO. Pick one of the following. + +### Preferred: `--headed --profile ` (one-time SSO) + +```bash +# First run — interactive: user logs in via SSO once; cookies persist in : +agent-browser --headed --profile ./.agent-browser-profile open + +# Subsequent runs — same profile, no login prompt: +agent-browser --profile ./.agent-browser-profile open +``` + +Use this by default. Least disruptive — it doesn't touch the user's regular Chrome. + +### Alternative: `--auto-connect` to an existing Chrome + +Only viable if Chrome is **already** running with `--remote-debugging-port=9222`. A normal Chrome window does **not** have this flag, and you cannot add it to a running Chrome — relaunching the binary with the flag prints `Opening in existing browser session` and silently no-ops. + +To use this path: + +```bash +# Fully quit Chrome first (closing the window is not enough): +osascript -e 'quit app "Google Chrome"' + +# Relaunch with the debug port: +open -a "Google Chrome" --args --remote-debugging-port=9222 + +# Then connect: +agent-browser --auto-connect open +``` + +Disruptive if the user has working tabs. Only suggest this if `--headed --profile` won't work. + +### Sharing a logged-in session across runs / CI + +Use `agent-browser state save` after a successful login and `agent-browser state load` in subsequent runs. Run `agent-browser skills get core` for the full auth-vault surface. + +## Driving with agent-browser + +Once a session is open (local or deployed), the browser persists across commands: + +```bash +agent-browser snapshot -i # see interactive elements as @e1, @e2, ... +agent-browser screenshot home.png # capture for inspection +# ... interact with refs from the snapshot ... +agent-browser close +``` + +For the full agent-browser surface (refs, waits, find-by-role, auth vault), run `agent-browser skills get core`. + +**Daemon flag-ignore gotcha**: when the agent-browser daemon is already running, `--headed` and `--profile` on a subsequent invocation are silently ignored: + +``` +⚠ --profile, --headed ignored: daemon already running. + Use 'agent-browser close' first to restart with new options. +``` + +Always run `agent-browser close` before changing launch flags. + +## Shutdown + +- **Local app**: send SIGINT (Ctrl+C) to `run-local`. It forwards SIGTERM to the app and waits up to **15 seconds** before SIGKILL — same grace window the Apps platform uses, so handlers behave the same locally and in prod. +- **agent-browser session**: `agent-browser close` to terminate the daemon. + +## What the local proxy actually does + +`run-local` starts an HTTP proxy on `localhost:` (default 8001) in front of the app (default `localhost:8000`). On every forwarded request it injects: + +| Header | Value | +|---|---| +| `X-Forwarded-Host` | `localhost` | +| `X-Forwarded-User` | CLI user's `userName` | +| `X-Forwarded-Email` | CLI user's primary email (or `userName` if none) | +| `X-Forwarded-Preferred-Username` | CLI user's `displayName` | +| `X-Real-Ip` | `127.0.0.1` | +| `X-Request-Id` | fresh UUID per request | + +These are the same headers the Apps platform's auth proxy sets in production, so AppKit (and any framework that reads `X-Forwarded-*`) treats the request as authenticated. The identity comes from `WorkspaceClient.CurrentUser.Me()` — i.e. whichever profile is active. + +The proxy injects identity headers but **not** an OBO user token. AppKit plugins or app code that requires an OBO token (anything calling `.asUser(req)` or reading a `user_api_scopes` token) will fail under `run-local`. For OBO-dependent flows, drive the deployed app instead. + +## Gotchas + +| Symptom | Cause | Fix | +|---|---|---| +| App redirects to OAuth instead of loading (local mode) | Framework isn't honoring `X-Forwarded-*`, or you bypassed the proxy and hit `--app-port` directly | Hit the **proxy** port (`--port`, default 8001), not the app port (`--app-port`, default 8000). For AppKit, ensure the version respects forwarded headers in dev. | +| `address already in use` on start | Default port 8001 (or 8000) is taken | Pass `--port` and/or `--app-port` to free values. | +| App keeps running after Ctrl+C | App ignored SIGTERM | Wait 15s — `run-local` escalates to SIGKILL. Fix the handler in the app. | +| Wrong user identity in the app | `databricks` is using a different profile than expected | Pass `--profile ` to `run-local`; verify with `databricks current-user me --profile `. | +| `prepare-environment` fails | `uv` not installed | Install `uv`, or skip the flag and prepare deps yourself. For Node-only apps `npm install` is enough. | +| `... defined in app.yaml with valueFrom property and can't be resolved locally` | `app.yaml` references bundle resources (warehouse, Genie space) that don't resolve outside a deployed bundle | Pass each `valueFrom` env var as `--env KEY=value`; values live in `databricks.yml` under `targets..variables`. | +| `app.yaml` not found / spec not picked up | AppKit scaffolds use `app.yaml`; older defaults look for `app.yml` | Pass `--entry-point app.yaml`. | +| AppKit app crashes on `run-local` with `AuthenticationError: Missing user token in request headers` | Proxy injects `X-Forwarded-*` identity but no OBO token; plugins requiring a user token reject this | Either run `npm run dev` instead, or drive the **deployed** app. The local proxy can't synthesize an OBO token. | +| Deployed mode: `No running Chrome instance found. Launch Chrome with --remote-debugging-port` | A regular Chrome window has no debug port. Relaunching Chrome with the flag while it's already running just opens a new tab in the existing instance (`Opening in existing browser session`) — it does NOT add the flag | Fully quit Chrome (`osascript -e 'quit app "Google Chrome"'`), then `open -a "Google Chrome" --args --remote-debugging-port=9222`. Or simpler: switch to `--headed --profile`. | +| `⚠ --profile, --headed ignored: daemon already running` | A previous agent-browser run left the daemon up | `agent-browser close` first, then re-open with the desired flags. | + +## Two-port summary (local mode — don't mix these up) + +- **`--port` (default 8001)** — the **proxy**. This is the URL you give to `agent-browser` and the URL printed by `run-local`. All traffic here gets the `X-Forwarded-*` headers. +- **`--app-port` (default 8000)** — the **app's own bind port**. The proxy forwards to this. Hitting it directly skips auth header injection — usually not what you want. diff --git a/skills/databricks-apps/references/appkit/overview.md b/skills/databricks-apps/references/appkit/overview.md index a0874e3..5d1662f 100644 --- a/skills/databricks-apps/references/appkit/overview.md +++ b/skills/databricks-apps/references/appkit/overview.md @@ -130,6 +130,7 @@ Do not guess paths — run without args first, then pick from the index. | Add Genie chat | [Genie](genie.md) — space creation, plugin setup, frontend components | | Call ML model serving endpoints | [Model Serving](model-serving.md) — resource declaration, tRPC query pattern | | Trigger / monitor Lakeflow Jobs from the app | [Jobs](jobs.md) — env discovery, JobHandle API, SSE streaming | +| Render / drive the running app in a browser | [agent-browser](agent-browser.md) — local (`run-local` proxy) and deployed (`--headed --profile`) modes for visual checks | ## Critical Rules From 59b10caf46602aebb74759e1bce83b030c26d9d4 Mon Sep 17 00:00:00 2001 From: Atila Fassina Date: Mon, 4 May 2026 18:54:27 +0200 Subject: [PATCH 2/2] refactor(appkit): reframe agent-browser skill around deployed apps Driving a deployed app with `--headed --profile ` is now the primary path; `npm run dev` is the simple local UI iteration path; `databricks apps run-local` is demoted to an edge case for testing platform header-injection behavior locally. Also replaces the vague "install per the team's instructions" punt with concrete `npm install -g agent-browser` (and pnpm/yarn equivalents) in the pre-flight check. Co-authored-by: Isaac --- .../references/appkit/agent-browser.md | 157 ++++++++++-------- .../references/appkit/overview.md | 2 +- 2 files changed, 90 insertions(+), 69 deletions(-) diff --git a/skills/databricks-apps/references/appkit/agent-browser.md b/skills/databricks-apps/references/appkit/agent-browser.md index 5cdf889..5b2ef3d 100644 --- a/skills/databricks-apps/references/appkit/agent-browser.md +++ b/skills/databricks-apps/references/appkit/agent-browser.md @@ -1,66 +1,40 @@ # agent-browser: Render a Running App for Visual Inspection -Use this when you (or an agent) need to **load a Databricks app in a browser and interact with it programmatically** — to verify a UI change, capture a screenshot, or drive a smoke flow. +Use this when you (or an agent) need to **load a Databricks app in a browser and interact with it programmatically** — verify a UI change, capture a screenshot, drive a smoke flow. -There are two modes. Pick the right one before scripting: +The primary target is a **deployed app URL** (real OAuth/SSO). For local UI iteration, the right path is the app's own Vite dev server (`npm run dev`) — no platform proxy, no auth, instant HMR. The `databricks apps run-local` proxy is an edge case for testing platform header-injection behavior locally without deploying; covered at the bottom. -| | **Local app** | **Deployed app** | -|---|---|---| -| What you're driving | `databricks apps run-local` on `localhost:` | A real `https://..databricksapps.com` URL | -| Auth | None — proxy injects `X-Forwarded-*` identity | Real OAuth/SSO required | -| Default command | `agent-browser open http://localhost:8001` | `agent-browser --headed --profile open ` (first run); plain `agent-browser open ` after | -| When | Iterating on the app from a checkout | Verifying what's actually deployed in staging/prod | +| | **Deployed app** (primary) | **Local Vite dev server** | **Local platform proxy** (edge case) | +|---|---|---|---| +| When | Verify what's actually in staging/prod | Iterate on UI code with HMR | Test how the app behaves under the platform's auth-header proxy without deploying | +| Auth | Real OAuth/SSO | None | None — proxy injects fake `X-Forwarded-*` | +| Run command | (already deployed) | `npm run dev` | `databricks apps run-local --entry-point app.yaml` | +| agent-browser command | `agent-browser --headed --profile open ` (first run); plain `agent-browser open ` after | `agent-browser open http://localhost:` | `agent-browser open http://localhost:8001` | -The browser daemon persists across commands either way; the only thing that changes is how the session is authenticated. +The browser daemon persists across commands; only the auth path differs. ## When NOT to use this -- **Iterating on AppKit UI code** (HMR, fast reload) — use `npm run dev` from the app directory instead. `run-local` runs the app the way the platform runs it, not the way Vite does. -- **Verifying real per-user OBO behavior on local** — the injected identity is the CLI-authenticated user, not a per-session user. Fine for visualization, not for OBO permission testing. For OBO, drive the deployed app instead. -- **Non-visual smoke tests** — for AppKit apps the scaffold ships `tests/smoke.spec.ts` (Playwright); see [testing.md](../testing.md). +- **Non-visual smoke tests** — for AppKit, the scaffold ships `tests/smoke.spec.ts` (Playwright); see [testing.md](../testing.md). +- **Verifying real per-user OBO behavior** — only the deployed app gives you a real OBO token. Neither Vite nor `run-local` will. ## Pre-flight checks Run these before anything else. If any fail, surface the failing check and stop — don't try to fix forward. -1. **`databricks` on PATH and authenticated** — `databricks current-user me --profile ` should print a user. If not, run `databricks auth login --host ` and stop. -2. **`agent-browser` on PATH** — `command -v agent-browser`. If missing, install per the team's instructions and stop. -3. **(Local mode only) App spec present** — `app.yaml` (AppKit scaffolds) or `app.yml` in the working directory, OR pass `--entry-point `. If neither, ask the user which directory the app lives in. -4. **(Local mode only) Proxy port free** — default is 8001. If `lsof -i :8001` shows anything, pick a free port and pass `--port `. Remember that port when invoking agent-browser. - -## Driving a local app (`run-local` proxy) - -From the app directory: - -```bash -# First run, when deps aren't installed (requires `uv`; for Node-only apps `npm install` is enough — skip the flag): -databricks apps run-local --prepare-environment --profile +1. **`agent-browser` on PATH** — `command -v agent-browser`. If missing, install it (it's a public npm package): + ```bash + npm install -g agent-browser + # or: pnpm add -g agent-browser + # or: yarn global add agent-browser + ``` + Then re-check with `command -v agent-browser` and `agent-browser --version`. Surface the failing check to the user before continuing — don't try to script around a missing binary. +2. **(Deployed mode)** the user can navigate to the deployed app URL in a regular browser and log in via SSO — that is the login `--headed --profile` will reproduce once. +3. **(Deployed mode, if discovering the URL)** `databricks` on PATH and authenticated for the workspace — `databricks apps list --profile `. -# Subsequent runs: -databricks apps run-local --profile +## Driving a deployed app (primary) -# AppKit apps use app.yaml (not app.yml): -databricks apps run-local --entry-point app.yaml --profile - -# Custom ports: -databricks apps run-local --port 8001 --app-port 8000 --entry-point app.yaml --profile -``` - -`run-local` runs in the foreground and prints `To access your app go to http://localhost:` once the proxy is listening. Run it in the background (or a separate shell) so you can drive `agent-browser` against it. - -If `app.yaml` declares env vars with `valueFrom` (referencing bundle resources like SQL warehouse IDs or Genie space IDs), `run-local` will exit with `... defined in app.yaml with valueFrom property and can't be resolved locally`. Pass each as `--env KEY=value`; values come from `databricks.yml` `targets..variables`. - -Wait until the proxy is reachable before scripting against it: - -```bash -until curl -fsS "http://localhost:8001/" -o /dev/null; do sleep 1; done -``` - -Then drive it with `agent-browser open http://localhost:8001` (see "Driving with agent-browser" below). - -## Driving a deployed app (real URL, real auth) - -For URLs like `https://..databricksapps.com`, `run-local`'s header-injection trick doesn't apply — the platform demands real OAuth/SSO. Pick one of the following. +For URLs like `https://..databricksapps.com`, the platform demands real OAuth/SSO. Pick one of the following. ### Preferred: `--headed --profile ` (one-time SSO) @@ -99,7 +73,7 @@ Use `agent-browser state save` after a successful login and `agent-browser state ## Driving with agent-browser -Once a session is open (local or deployed), the browser persists across commands: +Once a session is open, the browser persists across commands: ```bash agent-browser snapshot -i # see interactive elements as @e1, @e2, ... @@ -119,12 +93,27 @@ For the full agent-browser surface (refs, waits, find-by-role, auth vault), run Always run `agent-browser close` before changing launch flags. -## Shutdown +## Driving a local Vite dev server + +For UI iteration, run the app's own dev server and point agent-browser at it: + +```bash +# In the app directory: +npm run dev + +# In another shell, once Vite prints "Local: http://localhost:/": +agent-browser open http://localhost: +``` + +No auth. No proxy. Just Vite (and, for AppKit, the backing tRPC server). This is the right path when you're iterating on UI code and don't need real platform auth, `X-Forwarded-*` headers, or OBO tokens. The exact port comes from Vite's startup output — don't hardcode it. + +## Edge case: testing platform proxy behavior locally (`databricks apps run-local`) + +Use this **only** when you need to verify how the app behaves under the deployed platform's auth-header proxy without actually deploying — e.g. checking that the app correctly reads `X-Forwarded-*`, behaves under prod-style service-principal-only execution, or is served the way the runtime serves it. -- **Local app**: send SIGINT (Ctrl+C) to `run-local`. It forwards SIGTERM to the app and waits up to **15 seconds** before SIGKILL — same grace window the Apps platform uses, so handlers behave the same locally and in prod. -- **agent-browser session**: `agent-browser close` to terminate the daemon. +It is **not** the right tool for normal UI iteration (use `npm run dev`) and it is **not** the right tool for full deployed-app verification (use the real deployed URL). -## What the local proxy actually does +### What the proxy does `run-local` starts an HTTP proxy on `localhost:` (default 8001) in front of the app (default `localhost:8000`). On every forwarded request it injects: @@ -137,26 +126,58 @@ Always run `agent-browser close` before changing launch flags. | `X-Real-Ip` | `127.0.0.1` | | `X-Request-Id` | fresh UUID per request | -These are the same headers the Apps platform's auth proxy sets in production, so AppKit (and any framework that reads `X-Forwarded-*`) treats the request as authenticated. The identity comes from `WorkspaceClient.CurrentUser.Me()` — i.e. whichever profile is active. +These match the headers the Apps platform's auth proxy sets in production, so AppKit (and any framework that reads `X-Forwarded-*`) treats the request as authenticated. Identity comes from `WorkspaceClient.CurrentUser.Me()` for the active profile. -The proxy injects identity headers but **not** an OBO user token. AppKit plugins or app code that requires an OBO token (anything calling `.asUser(req)` or reading a `user_api_scopes` token) will fail under `run-local`. For OBO-dependent flows, drive the deployed app instead. +The proxy injects identity headers but **not** an OBO user token. Plugins or app code requiring an OBO token (anything calling `.asUser(req)` or reading a `user_api_scopes` token) will fail under `run-local` — drive the deployed app instead. + +### Pre-flight (run-local only) + +1. **App spec present** — `app.yaml` (AppKit) or `app.yml` in the working directory, OR pass `--entry-point `. +2. **Proxy port free** — default 8001. If `lsof -i :8001` shows anything, pick a free port and pass `--port `. + +### Running + +```bash +# AppKit apps use app.yaml: +databricks apps run-local --entry-point app.yaml --profile + +# First-time, if Python deps aren't installed (requires `uv`; Node-only apps just need `npm install`): +databricks apps run-local --prepare-environment --entry-point app.yaml --profile + +# Custom ports: +databricks apps run-local --port 8001 --app-port 8000 --entry-point app.yaml --profile +``` + +If `app.yaml` declares env vars with `valueFrom` (referencing bundle resources like SQL warehouse IDs or Genie space IDs), `run-local` will exit with `... defined in app.yaml with valueFrom property and can't be resolved locally`. Pass each as `--env KEY=value`; values come from `databricks.yml` `targets..variables`. + +`run-local` runs in the foreground and prints `To access your app go to http://localhost:` once the proxy is listening. Run it in the background (or a separate shell), wait for the proxy to be reachable, then drive it: + +```bash +until curl -fsS "http://localhost:8001/" -o /dev/null; do sleep 1; done +agent-browser open http://localhost:8001 +``` + +### Two-port summary (don't mix these up) + +- **`--port` (default 8001)** — the **proxy**. This is the URL you give to `agent-browser` and the URL printed by `run-local`. All traffic here gets the `X-Forwarded-*` headers. +- **`--app-port` (default 8000)** — the **app's own bind port**. The proxy forwards to this. Hitting it directly skips auth header injection — usually not what you want. + +### Shutdown + +Send SIGINT (Ctrl+C) to `run-local`. It forwards SIGTERM to the app and waits up to **15 seconds** before SIGKILL — same grace window the Apps platform uses, so handlers behave the same locally and in prod. Then `agent-browser close` to terminate the daemon. ## Gotchas | Symptom | Cause | Fix | |---|---|---| -| App redirects to OAuth instead of loading (local mode) | Framework isn't honoring `X-Forwarded-*`, or you bypassed the proxy and hit `--app-port` directly | Hit the **proxy** port (`--port`, default 8001), not the app port (`--app-port`, default 8000). For AppKit, ensure the version respects forwarded headers in dev. | -| `address already in use` on start | Default port 8001 (or 8000) is taken | Pass `--port` and/or `--app-port` to free values. | -| App keeps running after Ctrl+C | App ignored SIGTERM | Wait 15s — `run-local` escalates to SIGKILL. Fix the handler in the app. | -| Wrong user identity in the app | `databricks` is using a different profile than expected | Pass `--profile ` to `run-local`; verify with `databricks current-user me --profile `. | -| `prepare-environment` fails | `uv` not installed | Install `uv`, or skip the flag and prepare deps yourself. For Node-only apps `npm install` is enough. | -| `... defined in app.yaml with valueFrom property and can't be resolved locally` | `app.yaml` references bundle resources (warehouse, Genie space) that don't resolve outside a deployed bundle | Pass each `valueFrom` env var as `--env KEY=value`; values live in `databricks.yml` under `targets..variables`. | -| `app.yaml` not found / spec not picked up | AppKit scaffolds use `app.yaml`; older defaults look for `app.yml` | Pass `--entry-point app.yaml`. | -| AppKit app crashes on `run-local` with `AuthenticationError: Missing user token in request headers` | Proxy injects `X-Forwarded-*` identity but no OBO token; plugins requiring a user token reject this | Either run `npm run dev` instead, or drive the **deployed** app. The local proxy can't synthesize an OBO token. | | Deployed mode: `No running Chrome instance found. Launch Chrome with --remote-debugging-port` | A regular Chrome window has no debug port. Relaunching Chrome with the flag while it's already running just opens a new tab in the existing instance (`Opening in existing browser session`) — it does NOT add the flag | Fully quit Chrome (`osascript -e 'quit app "Google Chrome"'`), then `open -a "Google Chrome" --args --remote-debugging-port=9222`. Or simpler: switch to `--headed --profile`. | | `⚠ --profile, --headed ignored: daemon already running` | A previous agent-browser run left the daemon up | `agent-browser close` first, then re-open with the desired flags. | - -## Two-port summary (local mode — don't mix these up) - -- **`--port` (default 8001)** — the **proxy**. This is the URL you give to `agent-browser` and the URL printed by `run-local`. All traffic here gets the `X-Forwarded-*` headers. -- **`--app-port` (default 8000)** — the **app's own bind port**. The proxy forwards to this. Hitting it directly skips auth header injection — usually not what you want. +| Deployed mode: SSO login loop in `--headed` | Profile dir was reused after a credential rotation, or the workspace requires a fresh device check | Delete the profile dir and re-run with `--headed` to re-login from scratch. | +| `run-local` exits with `... defined in app.yaml with valueFrom property and can't be resolved locally` | `app.yaml` references bundle resources (warehouse, Genie space) that don't resolve outside a deployed bundle | Pass each `valueFrom` env var as `--env KEY=value`; values live in `databricks.yml` under `targets..variables`. | +| `run-local`: `app.yaml` not found / spec not picked up | AppKit scaffolds use `app.yaml`; older defaults look for `app.yml` | Pass `--entry-point app.yaml`. | +| `run-local`: AppKit app crashes with `AuthenticationError: Missing user token in request headers` | Proxy injects `X-Forwarded-*` identity but no OBO token; plugins requiring a user token reject this | Run `npm run dev` for UI iteration, or drive the **deployed** app for OBO behavior. The local proxy can't synthesize an OBO token. | +| `run-local`: app redirects to OAuth instead of loading | Framework isn't honoring `X-Forwarded-*`, or you bypassed the proxy and hit `--app-port` directly | Hit the **proxy** port (`--port`, default 8001), not the app port (`--app-port`, default 8000). | +| `run-local`: `address already in use` on start | Default port 8001 (or 8000) is taken | Pass `--port` and/or `--app-port` to free values. | +| `run-local`: app keeps running after Ctrl+C | App ignored SIGTERM | Wait 15s — `run-local` escalates to SIGKILL. Fix the handler in the app. | +| `run-local`: wrong user identity in the app | `databricks` is using a different profile than expected | Pass `--profile ` to `run-local`; verify with `databricks current-user me --profile `. | +| `run-local`: `prepare-environment` fails | `uv` not installed | Install `uv`, or skip the flag and prepare deps yourself. For Node-only apps `npm install` is enough. | diff --git a/skills/databricks-apps/references/appkit/overview.md b/skills/databricks-apps/references/appkit/overview.md index 5d1662f..a232e0b 100644 --- a/skills/databricks-apps/references/appkit/overview.md +++ b/skills/databricks-apps/references/appkit/overview.md @@ -130,7 +130,7 @@ Do not guess paths — run without args first, then pick from the index. | Add Genie chat | [Genie](genie.md) — space creation, plugin setup, frontend components | | Call ML model serving endpoints | [Model Serving](model-serving.md) — resource declaration, tRPC query pattern | | Trigger / monitor Lakeflow Jobs from the app | [Jobs](jobs.md) — env discovery, JobHandle API, SSE streaming | -| Render / drive the running app in a browser | [agent-browser](agent-browser.md) — local (`run-local` proxy) and deployed (`--headed --profile`) modes for visual checks | +| Render / drive the running app in a browser | [agent-browser](agent-browser.md) — primary path: drive a deployed app with `--headed --profile`; local Vite dev for UI iteration; `run-local` reserved as edge case | ## Critical Rules