Skip to content

Commit e13e55e

Browse files
authored
Merge pull request #219 from udecode/codex/fix-auth-env-runner
2 parents 36d976a + 3ec2d3b commit e13e55e

4 files changed

Lines changed: 169 additions & 20 deletions

File tree

.changeset/fix-auth-env-runner.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"kitcn": patch
3+
---
4+
5+
## Patches
6+
7+
- Fix auth env sync and local auth bootstrap so `kitcn add auth`, `kitcn env push`, and `kitcn dev --bootstrap` use the real Convex CLI entrypoint more reliably across runtimes and platforms.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
title: local auth env sync must use the real Convex CLI entrypoint
3+
date: 2026-04-17
4+
category: integration-issues
5+
module: kitcn cli env sync
6+
problem_type: integration_issue
7+
component: development_workflow
8+
symptoms:
9+
- auth bootstrap can fail at `generated/auth:getLatestJwks` even after the backend is ready
10+
- `kitcn add auth`, `kitcn env push`, or `kitcn dev --bootstrap` can print JWKS output and still exit non-zero on some runtime/platform combinations
11+
- Windows Bun runs can trip `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)` after the auth JWKS fetch path
12+
root_cause: wrong_api
13+
resolution_type: code_fix
14+
severity: high
15+
tags:
16+
- auth
17+
- env
18+
- convex
19+
- bootstrap
20+
- windows
21+
- bun
22+
---
23+
24+
# local auth env sync must use the real Convex CLI entrypoint
25+
26+
## Problem
27+
28+
Auth env sync already had the right lifecycle shape: prepare
29+
`BETTER_AUTH_SECRET`, then fetch `JWKS` after the generated auth runtime is
30+
live.
31+
32+
But the actual command runner for `env push` was still using the local
33+
`convex` bin shim, while the rest of the CLI already used
34+
`node <real convex/bin/main.js>`. That split made auth bootstrap
35+
runtime-sensitive.
36+
37+
## Symptoms
38+
39+
- `kitcn add auth --yes` can reach the final JWKS fetch, print returned key
40+
data, then still fail the command.
41+
- `kitcn dev --bootstrap` can fail on the same auth env sync leg after the
42+
backend reports ready.
43+
- Platform/runtime combos that shell through Bun on Windows can crash after the
44+
child command closes, even though the auth function returned usable output.
45+
46+
## What Didn't Work
47+
48+
- Treating the returned JWKS payload as the bug.
49+
The payload shape was fine for kitcn's auth config flow.
50+
- Blaming auth generation or Better Auth runtime wiring first.
51+
Fresh Start repros on mac passed cleanly once the command path behaved.
52+
- Letting env sync keep its own Convex invocation style.
53+
That kept the most platform-sensitive callsite off the hardened runner path
54+
already used elsewhere in the CLI.
55+
56+
## Solution
57+
58+
Make `runLocalConvexCommand(...)` execute the real Convex CLI entrypoint
59+
through Node instead of the local `convex` bin wrapper.
60+
61+
Before:
62+
63+
```ts
64+
await execa("convex", args, {
65+
cwd: options.cwd,
66+
localDir: options.cwd,
67+
preferLocal: true,
68+
reject: false,
69+
});
70+
```
71+
72+
After:
73+
74+
```ts
75+
await execa("node", [REAL_CONVEX_CLI_PATH, ...args], {
76+
cwd: options.cwd,
77+
reject: false,
78+
stdio: "pipe",
79+
});
80+
```
81+
82+
This makes auth env sync use the same Convex execution shape as
83+
`createBackendAdapter(...)` and `runBackendFunction(...)`.
84+
85+
## Why This Works
86+
87+
The auth flow itself was not the unstable part. The unstable part was using two
88+
different ways to launch Convex commands inside the same CLI:
89+
90+
1. backend-core paths used `node <real convex/bin/main.js>`
91+
2. env sync used the local `convex` bin wrapper
92+
93+
On friendly setups both work. On touchier runtime/platform combinations,
94+
especially Bun on Windows, the wrapper path can die after emitting usable
95+
stdout. Moving env sync onto the same Node-driven entrypoint removes that
96+
split-brain behavior.
97+
98+
## Prevention
99+
100+
- Keep local Convex calls on one execution path across `env`, `dev`, `add`,
101+
and backend helpers.
102+
- Add regression coverage for the command shape, not just the parsed output.
103+
- If a child command returns valid stdout but still exits non-zero, inspect the
104+
launcher first before rewriting the higher-level auth flow.
105+
106+
## Related Issues
107+
108+
- [auth-env-push-must-be-auth-aware-and-dev-bootstrap-must-stay-two-phase-20260324](/Users/zbeyens/git/better-convex/docs/solutions/integration-issues/auth-env-push-must-be-auth-aware-and-dev-bootstrap-must-stay-two-phase-20260324.md)
109+
- [dev-local-preflight-must-auto-upgrade-local-convex-backend-20260410](/Users/zbeyens/git/better-convex/docs/solutions/integration-issues/dev-local-preflight-must-auto-upgrade-local-convex-backend-20260410.md)
Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
1-
import { describe, expect, test } from 'bun:test';
2-
import { normalizeConvexCommandResult } from './convex-command';
1+
import { describe, expect, mock, test } from 'bun:test';
2+
3+
const CONVEX_CLI_ENTRY_RE = /convex[\\/]+bin[\\/]main\.js$/;
34

45
describe('cli/convex-command', () => {
5-
test('normalizeConvexCommandResult strips generic Convex nags from output', () => {
6-
const result = normalizeConvexCommandResult({
6+
test('runLocalConvexCommand executes the real Convex CLI through node', async () => {
7+
const execaStub = mock(async () => ({
78
exitCode: 0,
8-
stdout: [
9-
'Run `npx convex login` at any time to create an account and link this deployment.',
10-
'A minor update is available for Convex (1.33.0 → 1.34.0)',
11-
'Changelog: https://github.com/get-convex/convex-js/blob/main/CHANGELOG.md#changelog',
12-
'real stdout line',
13-
].join('\n'),
14-
stderr: [
15-
'Run `npx convex login` at any time to create an account and link this deployment.',
16-
'real stderr line',
17-
].join('\n'),
18-
});
9+
stderr: '',
10+
stdout: 'ok\n',
11+
}));
12+
13+
mock.module('execa', () => ({
14+
execa: execaStub,
15+
}));
16+
17+
const { CLEARED_CONVEX_ENV, runLocalConvexCommand } = await import(
18+
'./convex-command'
19+
);
20+
21+
const result = await runLocalConvexCommand(
22+
['run', 'generated/auth:getLatestJwks'],
23+
{
24+
cwd: '/tmp/kitcn-app',
25+
env: {
26+
FOO: 'bar',
27+
},
28+
}
29+
);
1930

2031
expect(result).toEqual({
2132
exitCode: 0,
22-
stdout: 'real stdout line',
23-
stderr: 'real stderr line',
33+
stderr: '',
34+
stdout: 'ok',
2435
});
36+
37+
expect(execaStub).toHaveBeenCalledWith(
38+
'node',
39+
[
40+
expect.stringMatching(CONVEX_CLI_ENTRY_RE),
41+
'run',
42+
'generated/auth:getLatestJwks',
43+
],
44+
expect.objectContaining({
45+
cwd: '/tmp/kitcn-app',
46+
env: expect.objectContaining({
47+
...CLEARED_CONVEX_ENV,
48+
FOO: 'bar',
49+
}),
50+
reject: false,
51+
stdio: 'pipe',
52+
})
53+
);
2554
});
2655
});

packages/kitcn/src/cli/convex-command.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createRequire } from 'node:module';
2+
import { dirname, join } from 'node:path';
13
import { execa } from 'execa';
24

35
export type ConvexCommandResult = {
@@ -12,6 +14,9 @@ const CONVEX_OUTPUT_NOISE_LINES = [
1214
/^Changelog: https:\/\/github\.com\/get-convex\/convex-js\/blob\/main\/CHANGELOG\.md#changelog$/,
1315
] as const;
1416
const CONVEX_OUTPUT_LINE_SPLIT_RE = /\r?\n/;
17+
const require = createRequire(import.meta.url);
18+
const convexPkg = require.resolve('convex/package.json');
19+
const REAL_CONVEX_CLI_PATH = join(dirname(convexPkg), 'bin/main.js');
1520

1621
export const CLEARED_CONVEX_ENV = {
1722
CONVEX_DEPLOYMENT: undefined,
@@ -78,16 +83,15 @@ export const runLocalConvexCommand = async (
7883
env?: Record<string, string | undefined>;
7984
}
8085
): Promise<ConvexCommandResult> => {
81-
const result = await execa('convex', args, {
86+
const result = await execa('node', [REAL_CONVEX_CLI_PATH, ...args], {
8287
cwd: options.cwd,
8388
env: {
8489
...process.env,
8590
...CLEARED_CONVEX_ENV,
8691
...options.env,
8792
},
88-
localDir: options.cwd,
89-
preferLocal: true,
9093
reject: false,
94+
stdio: 'pipe',
9195
});
9296

9397
return normalizeConvexCommandResult(result);

0 commit comments

Comments
 (0)