fix(cli): drop AO_PORT fallback in ao migrate daemon-url resolution

AO_PORT is overloaded — in a legacy environment it points at the legacy
Next.js dashboard (3000), not the rewrite daemon (3001). Falling back to it
could silently POST project-create payloads at the wrong server. Targeting
the rewrite daemon is now explicit: --daemon-url or AO_DAEMON_URL, else the
rewrite's 3001 default.

Ref: aoagents/ReverbCode#247

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
harshitsinghbhandari 2026-06-16 18:17:52 +05:30
parent ecd94f4af1
commit 37e0dbda44
No known key found for this signature in database
3 changed files with 23 additions and 13 deletions

View File

@ -21,7 +21,9 @@ function project(overrides: Partial<ProjectConfig> = {}): ProjectConfig {
name: "My Project",
path: "/repos/my-project",
defaultBranch: "main",
sessionPrefix: "mp",
// Empty by default so per-field assertions stay focused; a dedicated test
// covers sessionPrefix carry-over.
sessionPrefix: "",
...overrides,
} as ProjectConfig;
}
@ -79,9 +81,9 @@ describe("resolveDaemonUrl", () => {
process.env.AO_DAEMON_URL = "http://host:1234";
expect(resolveDaemonUrl()).toBe("http://host:1234");
});
it("builds a loopback url from AO_PORT", () => {
process.env.AO_PORT = "4567";
expect(resolveDaemonUrl()).toBe("http://127.0.0.1:4567");
it("ignores AO_PORT (overloaded with the legacy dashboard) and uses the default", () => {
process.env.AO_PORT = "3000";
expect(resolveDaemonUrl()).toBe(DEFAULT_DAEMON_URL);
});
it("uses the rewrite default when nothing is set", () => {
expect(resolveDaemonUrl()).toBe(DEFAULT_DAEMON_URL);
@ -153,6 +155,12 @@ describe("buildRewriteConfig", () => {
});
});
it("carries a non-empty sessionPrefix", () => {
expect(buildRewriteConfig(project({ sessionPrefix: "app" }), [])).toEqual({
sessionPrefix: "app",
});
});
it("carries env, symlinks, and postCreate verbatim", () => {
const config = buildRewriteConfig(
project({

View File

@ -21,7 +21,7 @@ export function registerMigrate(program: Command): void {
.option("--dry-run", "Parse and map the legacy registry, print the plan, write nothing")
.option(
"--daemon-url <url>",
"New AO daemon base URL (default http://127.0.0.1:3001; env AO_DAEMON_URL / AO_PORT)",
"New AO daemon base URL (default http://127.0.0.1:3001; env AO_DAEMON_URL)",
)
.action(async (opts: { dryRun?: boolean; daemonUrl?: string }) => {
const dryRun = opts.dryRun === true;
@ -51,7 +51,7 @@ export function registerMigrate(program: Command): void {
console.error(
chalk.dim(
"Start the new AO (the rewrite) first so its daemon is listening, then run `ao migrate`.\n" +
"Override the address with --daemon-url or the AO_DAEMON_URL / AO_PORT env vars.",
"Override the address with --daemon-url or the AO_DAEMON_URL env var.",
),
);
process.exit(1);

View File

@ -68,14 +68,17 @@ export const DEFAULT_DAEMON_URL = "http://127.0.0.1:3001";
/**
* Resolve the rewrite daemon base URL. Precedence: explicit flag AO_DAEMON_URL
* AO_PORT (loopback) the rewrite's hardcoded default.
* the rewrite's hardcoded default.
*
* We deliberately do NOT fall back to `AO_PORT`: that variable is overloaded
* (the legacy dashboard and the rewrite daemon both use it for "the port"), so
* in a legacy environment it usually points at the legacy Next.js dashboard.
* Targeting the rewrite daemon must be explicit and unambiguous.
*/
export function resolveDaemonUrl(explicit?: string): string {
if (explicit && explicit.trim().length > 0) return stripTrailingSlash(explicit.trim());
const url = process.env.AO_DAEMON_URL;
if (url && url.trim().length > 0) return stripTrailingSlash(url.trim());
const port = process.env.AO_PORT;
if (port && /^\d+$/.test(port.trim())) return `http://127.0.0.1:${port.trim()}`;
return DEFAULT_DAEMON_URL;
}
@ -389,15 +392,14 @@ export async function runMigrate(opts: MigrateOptions): Promise<MigrateSummary>
});
}
const ids = Object.keys(config.projects);
const entries = Object.entries(config.projects);
// Fail fast if the daemon is down (skip the probe on dry runs — they never hit it).
if (!opts.dryRun && ids.length > 0) {
if (!opts.dryRun && entries.length > 0) {
await apiRequest(fetchImpl, opts.daemonUrl, "GET", "/api/v1/projects");
}
for (const id of ids) {
const pc = config.projects[id]!;
for (const [id, pc] of entries) {
const plan = buildProjectPlan(id, pc);
if (!isValidRewriteProjectId(id)) {