Skip to content

Commit d6443a9

Browse files
authored
fix: preview env agent creation and auth failures (#3125)
* fix: preview env agent creation and auth failures Preview environments were missing INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET, forcing manage-ui server actions to fall back to fragile cookie forwarding for API auth. This caused false "Failed to create agent" errors and broken agent detail pages on most PR previews. Changes: - Inject INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET into both manage-ui and agents-api Vercel preview env vars (requires PREVIEW_MANAGE_API_BYPASS_SECRET GitHub secret to be created) - Add auth mode logging to manage-ui API calls so preview auth issues are visible in Vercel logs - Stop re-throwing auto-commit errors in branchScopedDb middleware - the route handler already succeeded and re-throwing turns a 201 into a 500 while the write is safely in Dolt's working set * fix: add retry to branchScopedDb auto-commit before swallowing Address review feedback: add one retry attempt for the auto-commit before falling back to swallowing the error. This reduces the chance of relying on the autoCommitPending recovery path. Also clarify in the comment that Dolt working set changes are per-branch and survive checkout to main + connection release.
1 parent 678a3c9 commit d6443a9

4 files changed

Lines changed: 73 additions & 37 deletions

File tree

.github/scripts/preview/upsert-vercel-preview-env.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require_env_vars \
1313
ANTHROPIC_API_KEY \
1414
BETTER_AUTH_SECRET \
1515
SPICEDB_PRESHARED_KEY \
16+
INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET \
1617
PR_BRANCH \
1718
API_URL \
1819
UI_URL
@@ -22,7 +23,7 @@ if ! [[ "${PR_BRANCH}" =~ ^[A-Za-z0-9._/-]+$ ]]; then
2223
exit 1
2324
fi
2425

25-
mask_env_vars ANTHROPIC_API_KEY BETTER_AUTH_SECRET SPICEDB_PRESHARED_KEY MANAGE_DB_URL RUN_DB_URL SPICEDB_ENDPOINT
26+
mask_env_vars ANTHROPIC_API_KEY BETTER_AUTH_SECRET SPICEDB_PRESHARED_KEY INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET MANAGE_DB_URL RUN_DB_URL SPICEDB_ENDPOINT
2627

2728
if [ -z "${MANAGE_DB_URL:-}" ] || [ -z "${RUN_DB_URL:-}" ] || [ -z "${SPICEDB_ENDPOINT:-}" ]; then
2829
require_env_vars \
@@ -89,6 +90,7 @@ upsert_env() {
8990
upsert_env "${VERCEL_MANAGE_UI_PROJECT_ID}" "INKEEP_AGENTS_API_URL" "${API_URL}"
9091
upsert_env "${VERCEL_MANAGE_UI_PROJECT_ID}" "PUBLIC_INKEEP_AGENTS_API_URL" "${API_URL}"
9192
upsert_env "${VERCEL_MANAGE_UI_PROJECT_ID}" "NEXT_PUBLIC_INKEEP_AGENTS_API_URL" "${API_URL}"
93+
upsert_env "${VERCEL_MANAGE_UI_PROJECT_ID}" "INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET" "${INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}"
9294

9395
upsert_env "${VERCEL_API_PROJECT_ID}" "INKEEP_AGENTS_API_URL" "${API_URL}"
9496
upsert_env "${VERCEL_API_PROJECT_ID}" "INKEEP_AGENTS_MANAGE_UI_URL" "${UI_URL}"
@@ -102,3 +104,4 @@ upsert_env "${VERCEL_API_PROJECT_ID}" "SPICEDB_ENDPOINT" "${SPICEDB_ENDPOINT}"
102104
upsert_env "${VERCEL_API_PROJECT_ID}" "SPICEDB_TLS_ENABLED" "false"
103105
upsert_env "${VERCEL_API_PROJECT_ID}" "BETTER_AUTH_SECRET" "${BETTER_AUTH_SECRET}"
104106
upsert_env "${VERCEL_API_PROJECT_ID}" "SPICEDB_PRESHARED_KEY" "${SPICEDB_PRESHARED_KEY}"
107+
upsert_env "${VERCEL_API_PROJECT_ID}" "INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET" "${INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}"

.github/workflows/preview-environments.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ name: Preview Environments
1313
# - PREVIEW_MANAGE_UI_PASSWORD=<admin password> (secret)
1414
# - PREVIEW_BETTER_AUTH_SECRET=<preview Better Auth secret> (secret)
1515
# - PREVIEW_SPICEDB_PRESHARED_KEY=<preview SpiceDB key> (secret)
16+
# - PREVIEW_MANAGE_API_BYPASS_SECRET=<preview manage API bypass secret> (secret)
1617
#
1718
# Repository variables:
1819
# - PREVIEW_ENVIRONMENTS_ENABLED=true|false
@@ -323,6 +324,7 @@ jobs:
323324
SPICEDB_ENDPOINT: ${{ needs.bootstrap-preview-auth.outputs.spicedb_endpoint }}
324325
BETTER_AUTH_SECRET: ${{ secrets.PREVIEW_BETTER_AUTH_SECRET }}
325326
SPICEDB_PRESHARED_KEY: ${{ secrets.PREVIEW_SPICEDB_PRESHARED_KEY }}
327+
INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET: ${{ secrets.PREVIEW_MANAGE_API_BYPASS_SECRET }}
326328
CI: true
327329
RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN || secrets.RAILWAY_TOKEN }}
328330
RAILWAY_PROJECT_ID: ${{ vars.RAILWAY_PROJECT_ID }}

agents-api/src/middleware/branchScopedDb.ts

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -113,42 +113,62 @@ export const branchScopedDbMiddleware = async (c: Context, next: Next) => {
113113
resolvedRef.type === 'branch' && operationSuccess && !projectDeleteOperation && !isReadMethod;
114114

115115
if (shouldCommit) {
116-
try {
117-
// Check if there are uncommitted changes
118-
const statusResult = await doltStatus(requestDb)();
119-
120-
// If there are uncommitted changes and the operation was successful, commit the changes
121-
if (statusResult.length > 0 && operationSuccess) {
122-
const path = c.req.path;
123-
const commitMessage = generateCommitMessage(method, path);
124-
125-
logger.info(
126-
{ branch: resolvedRef.name, message: commitMessage },
127-
'Auto-committing changes'
128-
);
129-
130-
await doltAddAndCommit(requestDb)({
131-
message: commitMessage,
132-
author: {
133-
name: userId ?? 'agents-api',
134-
email: userEmail ?? 'api@inkeep.com',
135-
},
136-
});
137-
138-
logger.info({ branch: resolvedRef.name }, 'Successfully committed changes');
139-
} else if (statusResult.length > 0 && !operationSuccess) {
140-
await doltReset(requestDb)();
141-
logger.info(
142-
{ branch: resolvedRef.name },
143-
'Successfully reset changes due to failed operation'
144-
);
116+
const path = c.req.path;
117+
const commitMessage = generateCommitMessage(method, path);
118+
const commitAuthor = {
119+
name: userId ?? 'agents-api',
120+
email: userEmail ?? 'api@inkeep.com',
121+
};
122+
123+
let committed = false;
124+
for (let attempt = 1; attempt <= 2 && !committed; attempt++) {
125+
try {
126+
const statusResult = await doltStatus(requestDb)();
127+
128+
if (statusResult.length > 0 && operationSuccess) {
129+
if (attempt === 1) {
130+
logger.info(
131+
{ branch: resolvedRef.name, message: commitMessage },
132+
'Auto-committing changes'
133+
);
134+
}
135+
136+
await doltAddAndCommit(requestDb)({
137+
message: commitMessage,
138+
author: commitAuthor,
139+
});
140+
141+
logger.info({ branch: resolvedRef.name }, 'Successfully committed changes');
142+
committed = true;
143+
} else if (statusResult.length > 0 && !operationSuccess) {
144+
await doltReset(requestDb)();
145+
logger.info(
146+
{ branch: resolvedRef.name },
147+
'Successfully reset changes due to failed operation'
148+
);
149+
committed = true;
150+
} else {
151+
committed = true;
152+
}
153+
} catch (error) {
154+
if (attempt < 2) {
155+
logger.warn(
156+
{ error, ...getDatabaseErrorLogContext(error), branch: resolvedRef.name, attempt },
157+
'Auto-commit attempt failed, retrying'
158+
);
159+
} else {
160+
// Do NOT re-throw: the route handler already returned a successful
161+
// response. Re-throwing replaces the handler's 2xx with a 500,
162+
// making the client think the operation failed when the write
163+
// is in Dolt's working set (persisted per-branch, survives
164+
// checkout to main and connection release). The next request's
165+
// checkoutBranch with autoCommitPending:true will commit it.
166+
logger.error(
167+
{ error, ...getDatabaseErrorLogContext(error), branch: resolvedRef.name },
168+
'Auto-commit failed after retry - writes remain in branch working set for next checkout'
169+
);
170+
}
145171
}
146-
} catch (error) {
147-
logger.error(
148-
{ error, ...getDatabaseErrorLogContext(error), branch: resolvedRef.name },
149-
'Failed to auto-commit changes — uncommitted writes will be lost on connection release'
150-
);
151-
throw error;
152172
}
153173
}
154174
} finally {

agents-manage-ui/src/lib/api/api-config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,21 @@ async function makeApiRequestInternal<T>(
6161
}
6262
}
6363

64+
const hasBypassSecret = Boolean(process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET);
65+
const authMode = hasBypassSecret ? 'bypass-secret' : cookieHeader ? 'cookie' : 'none';
66+
67+
if (authMode === 'none') {
68+
console.warn(
69+
`[api-config] No auth credentials for ${endpoint} - ` +
70+
'set INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET or ensure session cookies are forwarded'
71+
);
72+
}
73+
6474
const defaultHeaders: HeadersInit = {
6575
'Content-Type': 'application/json',
6676
...options.headers,
6777
...(cookieHeader && { Cookie: cookieHeader }),
68-
...(process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET && {
78+
...(hasBypassSecret && {
6979
Authorization: `Bearer ${process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}`,
7080
}),
7181
};
@@ -127,6 +137,7 @@ async function makeApiRequestInternal<T>(
127137
errorData,
128138
errorMessage,
129139
errorCode,
140+
authMode,
130141
});
131142

132143
throw new ApiError(

0 commit comments

Comments
 (0)