Skip to content

Fix stale service port plans#1416

Open
chyendongnhanh338 wants to merge 1 commit into
getpaseo:mainfrom
chyendongnhanh338:fix/1374-service-web-port-plan
Open

Fix stale service port plans#1416
chyendongnhanh338 wants to merge 1 commit into
getpaseo:mainfrom
chyendongnhanh338:fix/1374-service-web-port-plan

Conversation

@chyendongnhanh338

@chyendongnhanh338 chyendongnhanh338 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extend cached workspace service port plans when paseo.json gains additional service scripts.
  • Preserve existing service ports while allocating ports for newly declared services.
  • Add a bootstrap regression for adding web after a backend service plan already exists.

Closes #1374

Test plan

  • node ../../node_modules/vitest/vitest.mjs run src/server/workspace-service-port-registry.test.ts src/server/worktree-bootstrap.test.ts --config vitest.config.ts --bail=1
  • npm run lint -- packages/server/src/server/workspace-service-port-registry.ts packages/server/src/server/workspace-service-port-registry.test.ts packages/server/src/server/worktree-bootstrap.test.ts
  • npm run typecheck --workspace=@getpaseo/server
  • pre-commit: format, lint, full workspace typecheck

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

Fixes stale service port plans by replacing the early-exit cache hit with a while(true) loop that checks whether the cached plan covers the current service declarations, extending it when new services are added.

  • workspace-service-port-registry.ts: ensureWorkspaceServicePortPlan now calls planMatchesServiceDeclarations before returning a cached plan; mismatches (new services, removed services, explicit-port overrides) trigger a rebuild via createPendingWorkspaceServicePortPlan, which threads the existing plan through resolvePlannedServicePort to preserve known ports.
  • workspace-service-port-registry.test.ts: Adds three focused unit tests for the new extend, explicit-port-override, and service-removal cases; updates two existing tests that broke because services: [] now correctly triggers a rebuild.
  • worktree-bootstrap.test.ts: Adds an end-to-end regression for the exact scenario that motivated the fix — spawning backend-dev, then adding web to paseo.json and spawning it, asserting that both processes receive the backend port and that the web port is distinct.

Confidence Score: 5/5

Safe to merge; the core extension logic and concurrency loop are correct, tests cover the added behaviors, and no data-loss or correctness issues were found.

The while(true) loop correctly re-reads the stored plan on each iteration and either returns early, creates a new pending plan, or waits for an in-flight one — no infinite loop is possible and the concurrency semantics are sound. Port preservation, explicit-port override, and service removal are all correctly handled. The only finding is that resolveServicePort is now a structural duplicate of resolvePlannedServicePort with existingPlan: undefined, which is a cleanup opportunity but has no runtime impact.

No files require special attention.

Important Files Changed

Filename Overview
packages/server/src/server/workspace-service-port-registry.ts Adds while(true) loop with planMatchesServiceDeclarations check to extend cached plans instead of short-circuiting on any existing plan; new resolvePlannedServicePort preserves existing ports while allocating for newly declared services. Logic is correct, but resolveServicePort is now a strict duplicate of resolvePlannedServicePort with existingPlan: undefined.
packages/server/src/server/workspace-service-port-registry.test.ts Updates the stale-plan tests to cover extend (new services), explicit-port override, and service removal cases; adjusts two existing tests that broke because empty-services calls now trigger a rebuild.
packages/server/src/server/worktree-bootstrap.test.ts Adds a bootstrap regression test that verifies ports are preserved and extended when a second service is added to paseo.json after the first service plan has already been built.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ensureWorkspaceServicePortPlan called] --> B{existingPlan in cache?}
    B -- No --> D
    B -- Yes --> C{planMatchesServiceDeclarations?}
    C -- Yes --> RET1[Return copy of existing plan]
    C -- No: new/removed/explicit-port changed --> D

    D{pendingPlan in-flight?}
    D -- No --> E[createPendingWorkspaceServicePortPlan]
    E --> F[buildWorkspaceServicePortPlan]
    F --> G[resolvePlannedServicePort per service]
    G -- explicit port set --> H[Use declared port]
    G -- in existingPlan --> I[Reuse existing port]
    G -- new service --> J[allocatePort]
    H & I & J --> K[Store updated plan]
    K --> RET2[Return new plan]

    D -- Yes: another caller building --> L[await pendingPlan]
    L --> B
Loading

Reviews (2): Last reviewed commit: "fix(server): extend workspace service po..." | Re-trigger Greptile

Comment on lines +24 to +43
while (true) {
const existingPlan = workspaceServicePortPlans.get(options.workspaceId);
if (existingPlan && planIncludesServices(existingPlan, options.services)) {
return new Map(existingPlan);
}

let pendingPlan = pendingWorkspaceServicePortPlans.get(options.workspaceId);
if (!pendingPlan) {
pendingPlan = createPendingWorkspaceServicePortPlan({
workspaceId: options.workspaceId,
services: options.services,
allocatePort: options.allocatePort,
});
pendingWorkspaceServicePortPlans.set(options.workspaceId, pendingPlan);
}
let pendingPlan = pendingWorkspaceServicePortPlans.get(options.workspaceId);
if (!pendingPlan) {
pendingPlan = createPendingWorkspaceServicePortPlan({
workspaceId: options.workspaceId,
existingPlan,
services: options.services,
allocatePort: options.allocatePort,
});
pendingWorkspaceServicePortPlans.set(options.workspaceId, pendingPlan);
return new Map(await pendingPlan);
}

return new Map(await pendingPlan);
await pendingPlan;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Concurrent extension with disjoint new services is untested

The while(true) loop handles a subtle case: two callers concurrently extending the plan with different new services (e.g. caller A adds new-service-1, caller B adds new-service-2). Caller B awaits A's pending plan, then loops back—finds A's result doesn't cover new-service-2, creates a second pending plan using A's result as existingPlan, and allocates only new-service-2.

The existing concurrent test ("shares one first-plan build across concurrent callers") only exercises the initial-plan case where both callers request the same services. The new code's most non-obvious branch (the loop-back after await pendingPlan) is not covered. If a regression breaks this path, the while(true) loop could silently allocate a duplicate port or fail to include an expected service in the plan.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chyendongnhanh338 chyendongnhanh338 force-pushed the fix/1374-service-web-port-plan branch from e8bdee6 to f9fe118 Compare June 8, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Service 'web' is missing from workspace service port plan

1 participant