Skip to content

Commit 43e5f9a

Browse files
anandgupta42claude
andauthored
feat: support custom dbt profiles.yml path via DBT_PROFILES_DIR and project-local discovery (#605)
Closes #604 - Add `resolveProfilesPath()` with dbt-standard priority: explicit path > `DBT_PROFILES_DIR` env var > project-local `profiles.yml` > `<home>/.dbt/profiles.yml` - Warn when `DBT_PROFILES_DIR` is set but `profiles.yml` not found (graceful fallthrough) - Add `projectDir` param to `DbtProfilesParams` and expose in tool schema - Pass detected dbt project path from `project-scan` to `dbt.profiles` - Use `path.join(homedir(), ".dbt", "profiles.yml")` for OS-independent paths - Add 4 unit tests + 35 E2E tests covering priority chain, edge cases, adapter mapping - Update 6 doc files with platform-neutral path notation Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d0ada3 commit 43e5f9a

File tree

13 files changed

+800
-18
lines changed

13 files changed

+800
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export OPENAI_API_KEY=your_key # OpenAI
4747
altimate /discover
4848
```
4949

50-
`/discover` auto-detects dbt projects, warehouse connections (from `~/.dbt/profiles.yml`, Docker, environment variables), and installed tools (dbt, sqlfluff, airflow, dagster, and more). Skip this and start building — you can always run it later.
50+
`/discover` auto-detects dbt projects, warehouse connections (from `profiles.yml` — checks `DBT_PROFILES_DIR`, project directory, then `<home>/.dbt/`; plus Docker and environment variables), and installed tools (dbt, sqlfluff, airflow, dagster, and more). Skip this and start building — you can always run it later.
5151

5252
> **Headless / scripted usage:** `altimate --yolo` auto-approves all permission prompts. Not recommended with live warehouse connections.
5353

docs/docs/configure/warehouses.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,33 @@ The `/discover` command can automatically detect warehouse connections from:
467467

468468
| Source | Detection |
469469
|--------|-----------|
470-
| dbt profiles | Parses `~/.dbt/profiles.yml` |
470+
| dbt profiles | Searches for `profiles.yml` (see resolution order below) |
471471
| Docker containers | Finds running PostgreSQL, MySQL, SQL Server, and ClickHouse containers |
472472
| Environment variables | Scans for `SNOWFLAKE_ACCOUNT`, `PGHOST`, `DATABRICKS_HOST`, etc. |
473473

474+
### dbt profiles.yml resolution order
475+
476+
When discovering dbt profiles, altimate checks the following locations **in priority order** and uses the first one found:
477+
478+
| Priority | Location | Description |
479+
|----------|----------|-------------|
480+
| 1 | Explicit path | If you pass a `path` parameter to the `dbt_profiles` tool |
481+
| 2 | `DBT_PROFILES_DIR` env var | Standard dbt environment variable — set it to the directory containing your `profiles.yml` |
482+
| 3 | Project-local `profiles.yml` | A `profiles.yml` in your dbt project root (next to `dbt_project.yml`) |
483+
| 4 | `<home>/.dbt/profiles.yml` | The global default location (e.g., `~/.dbt/` on macOS/Linux, `%USERPROFILE%\.dbt\` on Windows) |
484+
485+
This means teams that keep `profiles.yml` in their project repo (a common pattern for CI/CD) will have it detected automatically — no extra configuration needed.
486+
487+
```bash
488+
# Option 1: Set the environment variable
489+
export DBT_PROFILES_DIR=/path/to/your/project
490+
491+
# Option 2: Just put profiles.yml next to dbt_project.yml
492+
# Copy from default location (macOS/Linux)
493+
cp ~/.dbt/profiles.yml ./profiles.yml
494+
altimate /discover
495+
```
496+
474497
See [Warehouse Tools](../data-engineering/tools/warehouse-tools.md) for the full list of environment variable signals.
475498

476499
## Testing Connections

docs/docs/data-engineering/tools/warehouse-tools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ env_bigquery | bigquery | GOOGLE_APPLICATION_CREDENTIALS
5353
| **Git** | `git` commands (branch, remote) |
5454
| **dbt project** | Walks up directories for `dbt_project.yml`, reads name/profile |
5555
| **dbt manifest** | Parses `target/manifest.json` for model/source/test counts |
56-
| **dbt profiles** | Bridge call to parse `~/.dbt/profiles.yml` |
56+
| **dbt profiles** | Searches for `profiles.yml`: `DBT_PROFILES_DIR` env var → project root → `<home>/.dbt/profiles.yml` |
5757
| **Docker DBs** | Bridge call to discover running PostgreSQL/MySQL/MSSQL containers |
5858
| **Existing connections** | Bridge call to list already-configured warehouses |
5959
| **Environment variables** | Scans `process.env` for warehouse signals (see table below) |

docs/docs/getting-started/quickstart-new.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ altimate
3434

3535
### Option A: Auto-detect from dbt profiles
3636

37-
If you have `~/.dbt/profiles.yml` configured:
37+
If you have a `profiles.yml` — either in your home directory's `.dbt/` folder, in your project repo, or pointed to by `DBT_PROFILES_DIR`:
3838

3939
```bash
4040
/discover
4141
```
4242

43-
Altimate reads your dbt profiles and creates warehouse connections automatically. You'll see output like:
43+
Altimate searches for `profiles.yml` in this order: `DBT_PROFILES_DIR` env var → project root (next to `dbt_project.yml`) → `<home>/.dbt/profiles.yml`. It reads your dbt profiles and creates warehouse connections automatically. You'll see output like:
4444

4545
```
4646
Found dbt project: jaffle_shop (dbt-snowflake)

docs/docs/getting-started/quickstart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ You can also set a smaller model for lightweight tasks like summarization:
189189
altimate /discover
190190
```
191191

192-
Auto-detects your dbt projects, warehouse credentials from `~/.dbt/profiles.yml`, running Docker containers, and environment variables (`SNOWFLAKE_ACCOUNT`, `PGHOST`, `DATABASE_URL`, etc.).
192+
Auto-detects your dbt projects, warehouse credentials from `profiles.yml` (checks `DBT_PROFILES_DIR`, then your project directory, then the default `<home>/.dbt/profiles.yml`), running Docker containers, and environment variables (`SNOWFLAKE_ACCOUNT`, `PGHOST`, `DATABASE_URL`, etc.).
193193

194194
### Manual configuration
195195

docs/docs/reference/troubleshooting.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ altimate --print-logs --log-level DEBUG
5555

5656
**Solutions:**
5757

58-
1. **If using dbt:** Run `altimate-dbt init` to set up the dbt integration. The CLI will use your `profiles.yml` automatically, so no separate connection config is needed.
58+
1. **If using dbt:** Run `/discover` — it automatically finds your `profiles.yml` from `DBT_PROFILES_DIR`, your project directory, or `<home>/.dbt/profiles.yml`. If your `profiles.yml` is in a custom location, set `DBT_PROFILES_DIR` to the directory containing it.
5959
2. **If not using dbt:** Add a connection via the `warehouse_add` tool, `~/.altimate-code/connections.json`, or `ALTIMATE_CODE_CONN_*` env vars.
6060
3. Test connectivity: use the `warehouse_test` tool with your connection name.
6161
4. Check that the warehouse hostname and port are reachable
62-
3. Verify the role/user has the required permissions
63-
4. For Snowflake: ensure the warehouse is not suspended
64-
5. For BigQuery: check that the service account has the required IAM roles
62+
5. Verify the role/user has the required permissions
63+
6. For Snowflake: ensure the warehouse is not suspended
64+
7. For BigQuery: check that the service account has the required IAM roles
6565

6666
### MCP Server Initialization Failures
6767

packages/opencode/src/altimate/native/connections/dbt-profiles.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,41 @@ function mapConfig(dbtType: string, dbtConfig: Record<string, unknown>): Connect
9595
return config
9696
}
9797

98+
/**
99+
* Resolve the profiles.yml path using dbt's standard priority order:
100+
* 1. Explicit path (if provided)
101+
* 2. DBT_PROFILES_DIR environment variable
102+
* 3. Project-local profiles.yml (in dbt project root)
103+
* 4. ~/.dbt/profiles.yml (default)
104+
*/
105+
function resolveProfilesPath(explicitPath?: string, projectDir?: string): string {
106+
if (explicitPath) return explicitPath
107+
108+
const envDir = process.env.DBT_PROFILES_DIR
109+
if (envDir) {
110+
const envPath = path.join(envDir, "profiles.yml")
111+
if (fs.existsSync(envPath)) return envPath
112+
// Warn when DBT_PROFILES_DIR is set but profiles.yml not found there —
113+
// dbt CLI would error here, we fall through for graceful discovery
114+
console.warn(`[dbt-profiles] DBT_PROFILES_DIR is set to "${envDir}" but no profiles.yml found there, falling through`)
115+
}
116+
117+
if (projectDir) {
118+
const projectPath = path.join(projectDir, "profiles.yml")
119+
if (fs.existsSync(projectPath)) return projectPath
120+
}
121+
122+
return path.join(os.homedir(), ".dbt", "profiles.yml")
123+
}
124+
98125
/**
99126
* Parse dbt profiles.yml and return discovered connections.
100127
*
101-
* @param profilesPath - Path to profiles.yml. Defaults to ~/.dbt/profiles.yml
128+
* @param profilesPath - Explicit path to profiles.yml
129+
* @param projectDir - dbt project root directory (for project-local profiles.yml)
102130
*/
103-
export async function parseDbtProfiles(profilesPath?: string): Promise<DbtProfileConnection[]> {
104-
const resolvedPath = profilesPath ?? path.join(os.homedir(), ".dbt", "profiles.yml")
131+
export async function parseDbtProfiles(profilesPath?: string, projectDir?: string): Promise<DbtProfileConnection[]> {
132+
const resolvedPath = resolveProfilesPath(profilesPath, projectDir)
105133

106134
if (!fs.existsSync(resolvedPath)) {
107135
return []

packages/opencode/src/altimate/native/connections/register.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ register("schema.inspect", async (params: SchemaInspectParams): Promise<SchemaIn
409409
// --- dbt.profiles ---
410410
register("dbt.profiles", async (params: DbtProfilesParams): Promise<DbtProfilesResult> => {
411411
try {
412-
const connections = await parseDbtProfiles(params.path)
412+
const connections = await parseDbtProfiles(params.path, params.projectDir)
413413
return {
414414
success: true,
415415
connections,

packages/opencode/src/altimate/native/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,8 @@ export interface DbtLineageResult {
908908

909909
export interface DbtProfilesParams {
910910
path?: string
911+
/** dbt project root directory — used to find project-local profiles.yml */
912+
projectDir?: string
911913
}
912914

913915
export interface DbtProfileConnection {

packages/opencode/src/altimate/tools/dbt-profiles.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import z from "zod"
2+
import { homedir } from "os"
3+
import { join } from "path"
24
import { Tool } from "../../tool/tool"
35
import { Dispatcher } from "../native"
46
import { isSensitiveField } from "../native/connections/credential-store"
57

8+
const DEFAULT_DBT_PROFILES = join(homedir(), ".dbt", "profiles.yml")
9+
610
export const DbtProfilesTool = Tool.define("dbt_profiles", {
711
description:
8-
"Discover dbt profiles from profiles.yml and map them to warehouse connections. Auto-detects Snowflake, BigQuery, Databricks, Postgres, Redshift, MySQL, DuckDB configurations.",
12+
`Discover dbt profiles from profiles.yml and map them to warehouse connections. Auto-detects Snowflake, BigQuery, Databricks, Postgres, Redshift, MySQL, DuckDB configurations. Searches: explicit path > DBT_PROFILES_DIR env var > project-local profiles.yml > ${DEFAULT_DBT_PROFILES}.`,
913
parameters: z.object({
10-
path: z.string().optional().describe("Path to profiles.yml (defaults to ~/.dbt/profiles.yml)"),
14+
path: z.string().optional().describe(`Explicit path to profiles.yml. If omitted, checks DBT_PROFILES_DIR, then project directory, then ${DEFAULT_DBT_PROFILES}`),
15+
projectDir: z.string().optional().describe("dbt project root directory. Used to find project-local profiles.yml next to dbt_project.yml"),
1116
}),
1217
async execute(args, ctx) {
1318
try {
1419
const result = await Dispatcher.call("dbt.profiles", {
1520
path: args.path,
21+
projectDir: args.projectDir,
1622
})
1723

1824
if (!result.success) {
@@ -28,7 +34,7 @@ export const DbtProfilesTool = Tool.define("dbt_profiles", {
2834
return {
2935
title: "dbt Profiles: No connections found",
3036
metadata: { success: true, connection_count: 0 },
31-
output: "No dbt profiles found. Ensure ~/.dbt/profiles.yml exists with valid configurations.",
37+
output: `No dbt profiles found. Ensure profiles.yml exists in your project directory, DBT_PROFILES_DIR, or ${DEFAULT_DBT_PROFILES}.`,
3238
}
3339
}
3440

0 commit comments

Comments
 (0)