Skip to content

fix(appkit): degrade typegen on build-time auth errors instead of failing the build - #561

Open
atilafassina wants to merge 2 commits into
mainfrom
typegen-issue
Open

fix(appkit): degrade typegen on build-time auth errors instead of failing the build#561
atilafassina wants to merge 2 commits into
mainfrom
typegen-issue

Conversation

@atilafassina

@atilafassina atilafassina commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

This PR shifts the behavior from "fail-first" to "degrade-first".
Before, we enumerated connection errors and would only allow the build if the error matched our allowlist, otherwise - even with available types, build would consider the warehouse was unreacheable and/or error was not transient.

This PR allows build to continues except for 404 and 400 will cause a fatal error and thus block the build, other status codes will atempt the build if types are present on the source artifact.

This makes the reject path symmetric with preflight and immune to the next statusless error class.

extra context:

Problem

When deploying an AppKit app, the build runs appkit generate-types --wait (blocking mode) in a prebuild hook. If the build-time identity can't execute a DESCRIBE against the SQL warehouse (e.g. PERMISSION_DENIED), the failure was recorded as a fatal error and thrown before the committed-types gate — so the deploy build failed even though generated types were already committed to the repo.

Root cause: the per-query (query-registry.ts) and per-metric-view (mv-registry/sync.ts) DESCRIBE-reject paths only degraded on a hard-coded connectivity allowlist. Everything else — including auth — became a fatalErrors entry thrown at index.ts, bypassing the committed-types fallback. This was asymmetric with the preflight path, which already routes non-404/400 failures to degrade. The observed error also carried no numeric HTTP status ({ error_code: "PERMISSION_DENIED", message: <hash> }), so a status/allowlist-based classifier couldn't identify it at all.

Fix: degrade-by-default, with a deny-list

Instead of enumerating which error classes are allowed to degrade (a losing game — the next SDK error shape without a status reopens the hole), the DESCRIBE reject path now degrades by default and keeps an explicit deny-list of what stays fatal.

The distinction that actually matters at DESCRIBE:

  • Statement never ran (the reject path — connectivity, auth/permission, SDK/config, or any unrecognized throw) → degrade. The has-types gate reuses committed types, or crashes a fresh checkout with nothing to fall back to.
  • Deny-list — deterministic client errors, HTTP 404 (bad/typo'd warehouse id) and 400 (malformed request), via classifyBlockingFailurestay fatal, so real config errors surface.
  • Statement ran and returned a SQL error (bad column/table) → unchanged; still fatal via the syntax-error branch.

…ling the build

A deploy build runs `appkit generate-types --wait` (blocking mode). When a
per-query or per-metric-view DESCRIBE rejected with anything the connectivity
allowlist didn't recognize, it was recorded as a fatal error and thrown before
the committed-types gate — so a build-time PERMISSION_DENIED failed the deploy
even though generated types were committed.

Build-time identity is not the app's runtime on-behalf-of identity, so a
build-time permission gap should degrade to committed types, not block a
deploy. Add `isAuthError` (HTTP 401/403 and Databricks error_code
PERMISSION_DENIED / UNAUTHENTICATED, including a statusless error_code carried
as a field or JSON body in the message) and route auth failures through the
same degrade path as connectivity in both the query and metric-view DESCRIBE
paths. Bad SQL, bad warehouse id (404), and malformed requests (400) stay
fatal. A fresh checkout with no committed types still crashes via the
has-types gate.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 1.0 MB (+1.8 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 1.0 MB (+1.7 KB) 377 KB (+578 B)
Type declarations 385 KB (+283 B) 136 KB (+127 B)
Source maps 2.1 MB (+3.7 KB) 705 KB (+1.2 KB)
Other 11 KB 3.7 KB
Total 3.5 MB (+5.7 KB) 1.2 MB (+1.9 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 95 KB (+117 B) 2.5 KB 97 KB (+117 B) external 311 KB (+439 B)
./beta 75 KB 457 B 75 KB external 225 KB
./testing 17 KB 0 B 17 KB external 50 KB
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 23 KB (+124 B) 0 B 23 KB (+124 B) external 65 KB (+439 B)

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 91 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 59 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.2 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 434 B
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 192 B
./beta databricks.js lazy 142 B
./beta index.js lazy 123 B
./testing index.js initial 17 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 23 KB

@databricks/appkit-ui

npm tarball (packed): 350 KB (+5 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 395 KB 132 KB
Type declarations 229 KB 84 KB (+1 B)
Source maps 766 KB 253 KB
CSS 16 KB 3.2 KB
Total 1.4 MB 473 KB (+1 B)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 5.3 KB 49 KB 55 KB 208 KB 14 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 432 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 KB

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 5.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 33528570528 -R databricks/appkit -n appkit-template-0.69.1-pr.2142851-typegen-issue-561 -D appkit-pr-561 \
  && unzip -o "appkit-pr-561/appkit-template-0.69.1-pr.2142851-typegen-issue-561.zip" -d "appkit-pr-561" \
  && databricks apps init --template "appkit-pr-561"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

…-list stays fatal

Replace the per-class allowlist at the DESCRIBE reject path with a
degrade-by-default policy plus an explicit deny-list. A statement that never
ran (connectivity, auth/permission, SDK/config, or any unrecognized throw) now
degrades so the has-types gate can reuse committed types; only the deny-list of
deterministic client errors — HTTP 404 (bad warehouse id) and 400 (malformed
request), via classifyBlockingFailure — stays fatal. This makes the reject path
symmetric with preflight and stops the whack-a-mole of enumerating every
error shape that lacks a status (the deploy PERMISSION_DENIED had no numeric
status). Bad SQL remains a ran-and-failed statement and stays fatal via the
syntax-error branch.

Applies to both the query and metric-view DESCRIBE paths. Tests that asserted
'unrecognized/auth reject -> fatal' are updated to the new intent; fatal-path
coverage switches to deny-list (404/400) errors. isAuthError is retained only
to label the degrade cause (accurate 'auth blocked' warning for a statusless
PERMISSION_DENIED).

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina
atilafassina marked this pull request as ready for review September 1, 2026 16:11
@atilafassina
atilafassina requested a review from a team as a code owner September 1, 2026 16:11
@atilafassina
atilafassina requested review from MarioCadenas and a lite review from Copilot September 1, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates AppKit’s type generator to degrade-by-default on build-time DESCRIBE failures (so committed/generated types can still be used) and to keep only a small deny-list (HTTP 400/404) as build-blocking, aligning behavior across query and metric-view typegen.

Changes:

  • Switch DESCRIBE rejection handling from allow-list “connectivity only” to deny-list “400/404 only”, degrading all other failures so the committed-types gate can decide.
  • Add/adjust tests to cover auth/statusless auth-shaped errors degrading, and deny-list errors remaining fatal.
  • Improve logging/cause reporting for degraded DESCRIBEs.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/appkit/src/type-generator/tests/mv-registry.test.ts Updates metric-view sync transience expectations and adds deny-list/auth/statusless coverage.
packages/appkit/src/type-generator/tests/index.test.ts Adjusts blocking-mode behavior test to use deny-list failure (400) for fatal path.
packages/appkit/src/type-generator/tests/generate-queries.test.ts Updates query DESCRIBE tests to reflect degrade-by-default and deny-list fatal handling.
packages/appkit/src/type-generator/tests/errors.test.ts Adds tests for auth detection and statusless error_code auth shapes.
packages/appkit/src/type-generator/query-registry.ts Changes rejected DESCRIBE handling to degrade-by-default with deny-list fatal classification and improved logging.
packages/appkit/src/type-generator/mv-registry/types.ts Updates MetricSyncFailure.transient doc to reflect “degrade vs fail-build” meaning.
packages/appkit/src/type-generator/mv-registry/sync.ts Uses classifyBlockingFailure to implement deny-list logic for metric-view DESCRIBE failures.
packages/appkit/src/type-generator/errors.ts Introduces isAuthError and refactors environmental cause labeling to recognize statusless auth error codes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +76 to +81
// The DESCRIBE never ran (fetcher threw). Degrade by default (connectivity,
// auth/permission, SDK/config) so the has-types gate can reuse committed
// types; only the deny-list of deterministic client errors (bad warehouse
// id 404, malformed request 400) surfaces as fatal — the same split the
// query path and preflight make. (Truncated/multi-chunk and zero-column
// responses are ran-and-failed, handled below and kept non-transient.)
Comment on lines 999 to 1003
if (mode === "blocking") {
hadEnvironmentalFailure = true;
environmentalCause = environmentalCause ?? "unreachable";
environmentalCause =
environmentalCause ?? classifyEnvironmentalCause(entry.reason);
}
Comment on lines +1052 to +1057
// (connectivity, auth/permission, SDK/config, and unrecognized throws) — the
// has-types gate reuses committed types. Only the deny-list of deterministic
// client errors (bad warehouse id 404, malformed request 400) and ran-and-failed
// responses (FAILED, zero rows, unparseable payload, zero columns, the
// truncation guard) are non-transient and surface as a build failure — the same
// split the query path and preflight make.
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.

2 participants