<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Ferraz</title>
    <description>The latest articles on DEV Community by Daniel Ferraz (@dcoferraz).</description>
    <link>https://dev.to/dcoferraz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1675636%2F82f120f6-99af-4c96-a196-42b7660b3584.png</url>
      <title>DEV Community: Daniel Ferraz</title>
      <link>https://dev.to/dcoferraz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dcoferraz"/>
    <language>en</language>
    <item>
      <title>What AI Agents Taught Me About Building Databricks Apps</title>
      <dc:creator>Daniel Ferraz</dc:creator>
      <pubDate>Wed, 21 Oct 2026 21:19:53 +0000</pubDate>
      <link>https://dev.to/dcoferraz/what-ai-agents-taught-me-about-building-databricks-apps-7ni</link>
      <guid>https://dev.to/dcoferraz/what-ai-agents-taught-me-about-building-databricks-apps-7ni</guid>
      <description>&lt;h1&gt;
  
  
  What AI Agents Taught Me About Building Databricks Apps
&lt;/h1&gt;

&lt;h3&gt;
  
  
  The agents wrote most of the code. Everything around it was still on me, and I think that's good news
&lt;/h3&gt;

&lt;p&gt;I've been building software since 2013.&lt;/p&gt;

&lt;p&gt;Most of that time on the frontend: React, components, the part of the product people actually touch.&lt;/p&gt;

&lt;p&gt;Then AI agents started writing code. A lot of it, and faster than me.&lt;/p&gt;

&lt;p&gt;And I had the same question I think a lot of us have right now: if the code writes itself, what am I here for?&lt;/p&gt;

&lt;p&gt;My answer so far is to make the job bigger. I'm working toward what I call a "Super Engineer": software engineering, data engineering and AI engineering under the same belt.&lt;/p&gt;

&lt;p&gt;I'm sharing it while it happens, wrong turns included :)&lt;/p&gt;

&lt;p&gt;This is one of those turns. We built a production Databricks App with AI coding agents doing a big part of the work. The code showed up fast. Getting it to run correctly took way longer.&lt;/p&gt;

&lt;p&gt;And every one of those extra hours went into knowledge the agents didn't have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5l24gw91q8tq5jsu8i5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz5l24gw91q8tq5jsu8i5.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The agent types faster than I ever will. So what do I bring to the table now?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem: the agent writes the code, the platform decides if it runs
&lt;/h2&gt;

&lt;p&gt;Databricks Apps let you host a web app right next to your data.&lt;/p&gt;

&lt;p&gt;Users sign in with the identity they already have, every query can run as that user, and Unity Catalog decides what each person can see. For an internal tool on top of governed tables, that's pretty close to ideal.&lt;/p&gt;

&lt;p&gt;Ask an agent for "a React app with an Express API that edits these tables" and you'll have working code in an afternoon.&lt;/p&gt;

&lt;p&gt;Whether it runs is a different story. That part depends on configuration: a bundle file, user API scopes, a warehouse binding and three separate permission systems.&lt;/p&gt;

&lt;p&gt;Most mistakes there are one line long. Many fail silently, or with an error that points somewhere else -&amp;gt; and an agent that reads the error and searches the code will confidently fix the wrong thing.&lt;/p&gt;

&lt;p&gt;Our app: React plus Node/Express in one container, reading and writing Unity Catalog tables through a SQL warehouse, deployed with a Databricks Asset Bundle and promoted from dev to sqa to prd through CI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3jjp4i2c7m35479p8xm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs3jjp4i2c7m35479p8xm.png" alt="The request path of a Databricks App, from the signed-in user through the Apps proxy, the React and API container and the SQL warehouse to Unity Catalog, with the traps we hit at each step" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One request, five hops, and a trap waiting at almost every one of them&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The walkthrough: where it broke
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Wiring user identity
&lt;/h3&gt;

&lt;p&gt;The design is on-behalf-of-user (OBO) authentication.&lt;/p&gt;

&lt;p&gt;The Databricks proxy forwards the signed-in user's token in &lt;code&gt;X-Forwarded-Access-Token&lt;/code&gt;. The API opens a SQL session with it on every request. The app's own service principal gets &lt;code&gt;CAN_USE&lt;/code&gt; on the warehouse and nothing else.&lt;/p&gt;

&lt;p&gt;First deploy: blank user names, empty tables.&lt;/p&gt;

&lt;p&gt;hm.&lt;/p&gt;

&lt;p&gt;The code was fine. The &lt;code&gt;sql&lt;/code&gt; scope was sitting under &lt;code&gt;config:&lt;/code&gt; in the bundle, where it's silently ignored. It belongs at the app level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;my_app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${var.deploy_app_name}&lt;/span&gt;
      &lt;span class="na"&gt;source_code_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;user_api_scopes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# app level, not under config:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring the scope only requests it. The workspace has to allow it and have user-token passthrough enabled, and the only real proof is the deployed app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks apps get &amp;lt;app&amp;gt;   &lt;span class="c"&gt;# effective_user_api_scopes must include "sql"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring environments
&lt;/h3&gt;

&lt;p&gt;We promoted to sqa and saw dev data.&lt;/p&gt;

&lt;p&gt;A committed &lt;code&gt;app.yaml&lt;/code&gt; was overriding the bundle's per-target settings, so every environment was running against the dev catalog. We deleted it and kept every environment value in the bundle targets.&lt;/p&gt;

&lt;p&gt;Then a bare warehouse id made every query return 404 (the driver needs &lt;code&gt;/sql/1.0/warehouses/&amp;lt;id&amp;gt;&lt;/code&gt;). And a catalog typo (&lt;code&gt;_qa&lt;/code&gt; instead of &lt;code&gt;_sqa&lt;/code&gt;) gave us empty screens and no error at all.&lt;/p&gt;

&lt;p&gt;These days the app logs its effective catalog, schema and warehouse at boot. That one line catches this kind of bug in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying
&lt;/h3&gt;

&lt;p&gt;Our first deployment pulled the code from a git branch and never started: "App process did not start within 10 minutes."&lt;/p&gt;

&lt;p&gt;A git snapshot only has tracked files, and our build output was gitignored. So we switched to the bundle and built at start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ci&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;node&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;server/dist/index.js"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next one: the pipeline went green and the app didn't change.&lt;/p&gt;

&lt;p&gt;With Asset Bundles, &lt;code&gt;bundle deploy&lt;/code&gt; uploads the code and &lt;code&gt;bundle run&lt;/code&gt; starts the new version. BUT our CI template only ran the second step for resource keys that matched its naming convention. Ours didn't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;databricks bundle deploy &lt;span class="nt"&gt;-t&lt;/span&gt; dev
databricks bundle run &amp;lt;app-resource-key&amp;gt; &lt;span class="nt"&gt;-t&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The npm install had its own surprises: a corporate registry pinned in &lt;code&gt;.npmrc&lt;/code&gt; that Databricks can't reach, an &lt;code&gt;omit=optional&lt;/code&gt; setting that dropped native binaries, and a missing &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening it to users
&lt;/h3&gt;

&lt;p&gt;Some users still saw nothing.&lt;/p&gt;

&lt;p&gt;Three permissions have to line up: app &lt;code&gt;CAN_USE&lt;/code&gt; (who can open the app), warehouse &lt;code&gt;CAN_USE&lt;/code&gt; (who can run queries), and Unity Catalog grants on each table, including &lt;code&gt;USE CATALOG&lt;/code&gt; and &lt;code&gt;USE SCHEMA&lt;/code&gt;. Each one fails on its own, and different teams usually own them.&lt;/p&gt;

&lt;p&gt;Our group check was also rejecting the right people. The workspace SCIM &lt;code&gt;/Me&lt;/code&gt; endpoint leaves out account-level groups synced from the identity provider. Running &lt;code&gt;SELECT is_account_group_member(:group)&lt;/code&gt; with the user's own token fixed it.&lt;/p&gt;

&lt;p&gt;And the first request after a quiet period sometimes failed with "Failed to open new session" while the warehouse woke up (30 seconds to five minutes on a classic one). Nothing had run yet, so retrying with backoff was safe. We added retries and a small "warehouse is starting" banner, and moving to a serverless warehouse removed most of those errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9vvi3h98y7y19msasjo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq9vvi3h98y7y19msasjo.png" alt="A team wiki page the agent skipped, and a terminal where npm run preflight passes every check" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agents couldn't learn from a wiki
&lt;/h2&gt;

&lt;p&gt;Each of these failures was easy to fix and took hours to find.&lt;/p&gt;

&lt;p&gt;The agents hit every single one. And they kept hitting them after we wrote the fixes down. They only stopped when a check failed on the mistake.&lt;/p&gt;

&lt;p&gt;That changed how I see my own role. I think the most valuable work in this project was knowing where the traps were, and turning that into something the agent has to pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: how you can skip the hard path
&lt;/h2&gt;

&lt;p&gt;Set up four things before your agent writes its first line.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ten rules in AGENTS.md
&lt;/h3&gt;

&lt;p&gt;Put short rules in an &lt;code&gt;AGENTS.md&lt;/code&gt; (or &lt;code&gt;CLAUDE.md&lt;/code&gt;) at the repo root, where the agent reads them first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run every query as the user. Unity Catalog is the security boundary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_api_scopes: [sql]&lt;/code&gt; goes at the app level. Check &lt;code&gt;effective_user_api_scopes&lt;/code&gt; after each deploy.&lt;/li&gt;
&lt;li&gt;Three permissions must line up: app &lt;code&gt;CAN_USE&lt;/code&gt;, warehouse &lt;code&gt;CAN_USE&lt;/code&gt;, table grants.&lt;/li&gt;
&lt;li&gt;Deploy through the bundle, never a git snapshot.&lt;/li&gt;
&lt;li&gt;An app deploy is &lt;code&gt;bundle deploy&lt;/code&gt; plus &lt;code&gt;bundle run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;app.yaml&lt;/code&gt;. Environment values live in bundle targets.&lt;/li&gt;
&lt;li&gt;Normalize the warehouse path to &lt;code&gt;/sql/1.0/warehouses/&amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Retry session-open errors with backoff, and tell users the warehouse is waking up.&lt;/li&gt;
&lt;li&gt;Fake databases are for tests only.&lt;/li&gt;
&lt;li&gt;Every deploy failure becomes a check.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. A preflight check before the first pipeline
&lt;/h3&gt;

&lt;p&gt;Turn each trap into a lint rule in a script that exits non-zero on errors: &lt;code&gt;user_api_scopes&lt;/code&gt; under &lt;code&gt;config:&lt;/code&gt;, &lt;code&gt;git_source&lt;/code&gt;, an &lt;code&gt;app.yaml&lt;/code&gt; with environment values, a missing lockfile, a pinned registry, &lt;code&gt;omit=optional&lt;/code&gt;, a personal &lt;code&gt;/Workspace/Users/...&lt;/code&gt; root path, secrets in plain env values, two targets sharing one workspace.&lt;/p&gt;

&lt;p&gt;The script should also list what it can't check (secret bindings, scope passthrough, the three grants), so the agent tells you who owns each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A template that already follows the rules
&lt;/h3&gt;

&lt;p&gt;Generating the app from a reviewed spec is way more reliable than asking an agent to remember forty rules.&lt;/p&gt;

&lt;p&gt;Ours asks for the app name, the catalog, warehouse and groups per environment, and the entities and fields. Then it generates an app with OBO sessions, group checks, cold-start retries, auditable writes, a bundle with dev, sqa and prd targets, grant scripts for the data owner, and tests that pass on day one.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A fixed order for "I see no data"
&lt;/h3&gt;

&lt;p&gt;Check app access, then the OBO scope, the warehouse, the Unity Catalog grants, the data, and only then the UI.&lt;/p&gt;

&lt;p&gt;Most fixes in the first four are admin actions, so the agent should pass them to the owner and leave grants alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63og81g8zlnla7ad9ybb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F63og81g8zlnla7ad9ybb.png" alt="Three overlapping circles for software, data and AI engineering, with an SE shield for Super Engineer where they meet" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Soooo, where does this leave me?
&lt;/h2&gt;

&lt;p&gt;After this setup, our deploys passed on the first run, and every "no data" report went through the same steps. The agents stopped asking "why won't it start?" and started asking "which admin owns this grant?"&lt;/p&gt;

&lt;p&gt;This project touched all three parts of the path I'm on: a software app, on governed data, built with AI agents.&lt;/p&gt;

&lt;p&gt;The agents did write most of the code. What made it work was understanding the platform underneath and writing that down in a way the agents had to follow.&lt;/p&gt;

&lt;p&gt;I find that really encouraging. It's work I can learn, and I believe you can too.&lt;/p&gt;

&lt;p&gt;If you're feeling that same fear about AI right now, I get it. I think there's still a lot of room for us. The job just looks a bit different.&lt;/p&gt;

&lt;p&gt;Huge thanks to the team I built this with - you know who you are :)&lt;/p&gt;

&lt;p&gt;Let me know in the comments: what are you learning next? Or which Databricks trap got you the worst?&lt;/p&gt;

&lt;p&gt;PS: everything from this article lives in bricksmith, the skill I built so my agents stop re-learning these traps. If it saves you a deploy or two, a ⭐ helps a lot :) &lt;br&gt;
&lt;a href="https://dev.tobricksmith"&gt;github.com/dcoferraz/bricksmith&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>databricks</category>
      <category>dataengineering</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
