Skip to content

perf(cli): drop the lsof port pre-flight from server-routed commands - #10673

Merged
davidfirst merged 5 commits into
masterfrom
perf-cli-drop-lsof-port-validation
Aug 28, 2026
Merged

perf(cli): drop the lsof port pre-flight from server-routed commands#10673
davidfirst merged 5 commits into
masterfrom
perf-cli-drop-lsof-port-validation

Conversation

@davidfirst

@davidfirst davidfirst commented Aug 28, 2026

Copy link
Copy Markdown
Member

Every command routed through bit-server spawned lsof twice before sending anything: once to find the pid listening on the port, then once more (or readlink /proc/<pid>/cwd on Linux) to read that pid's cwd and confirm the server belongs to this workspace. That is ~320ms of subprocess overhead on every command, and it directly affects VS Code extension latency.

The request already proves what the pre-flight was checking. Each server writes its bearer token into its own scope dir, so a port file left pointing at another workspace's server fails the token check and comes back 401; a dead port gives ECONNREFUSED. Both drop the stale port file and throw ServerIsNotRunning, which run-bit.ts already catches to fall back to running the command in-process — the same outcome the lsof check produced, minus the two spawns.

Measured

Server-routed bit status, median of 7, same workspace and machine, both arms compiled and measured back to back:

median range
before 626ms 607-642
after 304ms 302-311

~322ms / 51% per command.

Reliability (from review)

Removing the pre-flight lost a few things it was doing incidentally, all since restored:

  • Port validation. lsof -iTCP:NaN used to fail and route an unparseable port file into the in-process fallback. getExistingPort() now validates the parsed value explicitly. It deliberately does not delete the file — a torn read means the server is mid-write, and deleting would destroy a port file about to become valid (master deletes it today, so this is now strictly better).
  • Token rotation. A 401 was treated as proof of a stale port file, so a command spanning a server restart could delete the port file of a live server. The client now re-reads the token on 401 and retries once if it changed; only an unchanged token means the server really isn't ours. writeServerToken() runs before any route is registered, so a server able to answer with a 401 has already published its current token.
  • Response trust. Any 2xx JSON was returned as the command result, so a listener holding a stale port could suppress the real command. bit-server always answers with a { data, exitCode } object; anything else now fails safe by dropping the port file and running in-process.

Behavior checks

  • malformed port files (empty, whitespace, garbage, out-of-range, negative) -> fall back in-process, file preserved
  • port file pointing at a dead port -> falls back in-process, stale file removed
  • port file pointing at another workspace's server -> 401, falls back in-process, stale file removed
  • impostor listener answering with a valid-JSON non-object 200 -> real command still runs in-process, stale file removed
  • a workspace's own server -> still served, and keeps its port file after an unrelated cleanup

Note on server authentication

bit-server authenticates the client to the server but never the server to the client. That is pre-existing and platform-wide: on Windows getCwdByPid returns null, so isPortInUseForCurrentDir already returned true for any listener. The cwd comparison this PR removes was not an authentication mechanism either — cwd is attacker-controlled, since anyone able to bind the port can also cd into the workspace. Proper server-to-client auth (e.g. an HMAC over a client nonce) is worth doing on its own; it isn't something this change makes newly necessary.

cli-server-port keeps the full lsof validation. Its job is to answer "is there a usable server?" for external clients like the VS Code extension, which expect no output when there isn't one, so the two spawns are worth it for that one rarely-called command.

Every server-routed command spawned lsof twice (once to find the pid on the
port, once to read that pid's cwd) to prove the server belongs to this
workspace - ~309ms on every command. The request already proves it: each server
writes its token into its own scope dir, so a port file pointing at another
workspace's server gets a 401, and a dead port gets ECONNREFUSED. Both now drop
the stale port file and fall back to running in-process.

590ms -> 281ms for a server-routed 'bit status'. The cli-server-port command
keeps validating, since external clients rely on it reporting no port when
there is no usable server.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Remove lsof preflight from server-routed CLI commands

✨ Enhancement 🐞 Bug fix 🕐 Less than 10 minutes

Grey Divider

AI Description

• Eliminates repeated lsof checks from latency-sensitive server-routed commands.
• Uses authenticated request failures to detect stale or cross-workspace port files.
• Preserves full server validation for external cli-server-port consumers.
Diagram

graph TD
  Mode{"CLI command"} -- "Server routed" --> Port["Read port file"] --> API["Authenticated request"] --> Status{"HTTP outcome"}
  Mode -- "Port query" --> Validate["lsof validation"] --> Port
  Status -- "Success" --> Result["Server result"]
  Status -- "401/404/refused" --> Cleanup["Delete stale file"] --> Fallback["In-process fallback"]
Loading
High-Level Assessment

The request-first strategy is appropriate because bearer-token authentication already proves workspace ownership while connection and route failures prove server usability. Retaining lsof only for cli-server-port preserves external-client semantics without imposing subprocess latency on every routed command.

Files changed (1) +22 / -1

Enhancement (1) +22 / -1
server-commander.tsReplace routed-command lsof preflight with request-based validation +22/-1

Replace routed-command lsof preflight with request-based validation

• Server-routed commands now read the recorded port directly and rely on authenticated HTTP behavior to validate the target server. Unauthorized and missing-route responses remove the stale port file and raise ServerIsNotRunning, while cli-server-port continues using full process and cwd validation.

scopes/harmony/bit/server-commander.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 28, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Listener identity is unverified 🐞 Bug ⛨ Security
Description
Using the unvalidated port sends the workspace bearer token, command arguments, working directory,
and feature flags to whichever process has inherited a stale port. Since any successful JSON
response is trusted as the command result, an unrelated listener can also spoof success and prevent
the real command from running in-process.
Code

scopes/harmony/bit/server-commander.ts[R133-134]

+    const port = await this.getExistingPort();
 const url = `http://${resolveDialHost()}:${port}/api`;
Evidence
The changed call now trusts the raw port without the existing cwd ownership check. The resulting
request includes the bearer token and workspace-specific command data, while the response path
accepts any ok JSON; repository authentication only checks that an incoming client knows the token
and provides no reciprocal proof of server identity.

scopes/harmony/bit/server-commander.ts[128-190]
scopes/harmony/bit/server-commander.ts[412-435]
scopes/harmony/api-server/api-server.main.runtime.ts[130-154]
scopes/harmony/api-server/api-server.main.runtime.ts[320-365]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`runCommandWithHttpServer()` no longer verifies that the process listening on the stored port belongs to this workspace. A stale port may have been reassigned to an unrelated process, but the client sends that process the workspace bearer token and command request, then accepts any 2xx JSON response as authoritative.
## Issue Context
The bearer middleware authenticates clients to Bit; it does not authenticate the server to the client. Preserve the performance goal without sending sensitive data to an unverified listener—for example, add a lightweight challenge-response endpoint that proves possession of the workspace token before sending the bearer token and command, or retain ownership validation until equivalent server authentication exists. Ensure an invalid, missing, or timed-out proof removes the stale port file and triggers in-process fallback.
## Fix Focus Areas
- scopes/harmony/bit/server-commander.ts[128-190]
- scopes/harmony/api-server/api-server.main.runtime.ts[130-154]
- scopes/harmony/api-server/api-server.main.runtime.ts[320-365]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Invalid port blocks fallback ✓ Resolved 🐞 Bug ☼ Reliability
Description
Switching runCommandWithHttpServer() directly to getExistingPort() lets empty, partially
written, or malformed port-file contents become NaN or an unintended numeric prefix, producing a
generic fetch/URL failure instead of ServerIsNotRunning. The CLI then exits rather than falling
back in-process, and the stale file remains in place for every subsequent command.
Code

scopes/harmony/bit/server-commander.ts[123]

+    const port = await this.getExistingPort();
Evidence
The changed call bypasses the old path that checked whether a process owned the parsed port and
converted lookup failure to cleanup plus ServerIsNotRunning. getExistingPort() performs only
parseInt; the request handler converts only ECONNREFUSED, while other fetch failures are printed
and exit, and the producer uses a non-atomic direct overwrite that can expose truncated contents to
concurrent readers.

scopes/harmony/bit/server-commander.ts[123-160]
scopes/harmony/bit/server-commander.ts[382-417]
scopes/harmony/bit/server-forever.ts[171-209]
scopes/harmony/api-server/api-server.main.runtime.ts[247-253]
scopes/harmony/bit/server-commander.ts[96-103]
scopes/harmony/bit/run-bit.ts[30-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Server-routed commands now consume the parsed port without the validation formerly supplied by `getExistingUsedPort()`. Invalid or transiently partial file contents must be treated as stale discovery state so the command can fall back in-process.
## Issue Context
`getExistingPort()` uses unchecked `parseInt`, while the producer overwrites the discovery file directly rather than publishing it atomically.
## Fix Focus Areas
- scopes/harmony/bit/server-commander.ts[408-417]
- scopes/harmony/api-server/api-server.main.runtime.ts[247-253]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Restart deletes current port ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new 401/404 branch unconditionally removes the scope's port file even though the response may
belong to a stale request spanning a server restart. If a restarted instance has already published
its port and fresh token, the old request can receive 401 and delete that new instance's valid
discovery file, making later clients report no running server.
Code

scopes/harmony/bit/server-commander.ts[R171-173]

+    if (res.status === 401 || res.status === 404) {
+      await this.deleteServerPortFile();
+      throw new ServerIsNotRunning(port);
Evidence
The client reads the port and token separately, then the added status branch removes whatever port
file currently exists. Startup generates and overwrites a fresh token and later writes the listening
port, so an in-flight request carrying the prior token can hit the restarted server, receive its
documented 401, and remove the newly written discovery file.

scopes/harmony/bit/server-commander.ts[123-173]
scopes/harmony/bit/server-commander.ts[421-431]
scopes/harmony/api-server/api-server.main.runtime.ts[231-253]
scopes/harmony/api-server/api-server.main.runtime.ts[256-276]
scopes/harmony/api-server/api-server.main.runtime.ts[333-358]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A 401/404 response must not unconditionally delete discovery state that may have been replaced after the request began. Invalidate only the same server-state snapshot used for the request, or retry when the published port/token changed.
## Issue Context
Server startup overwrites both the token and port files, while `deleteServerPortFile()` removes the current path without checking which server instance published it. Comparing only the port is insufficient because a restart can reuse the same port; include an instance identifier or token in the guarded invalidation.
## Fix Focus Areas
- scopes/harmony/bit/server-commander.ts[146-173]
- scopes/harmony/bit/server-commander.ts[421-424]
- scopes/harmony/api-server/api-server.main.runtime.ts[247-276]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scopes/harmony/bit/server-commander.ts
Comment thread scopes/harmony/bit/server-commander.ts
Two reliability gaps from dropping the lsof pre-flight:

- getExistingPort parsed the file with an unchecked parseInt, so an empty or
  half-written file became NaN and surfaced as an opaque fetch error that exited
  instead of falling back in-process. Validate it and fall back. The file is
  deliberately not deleted - a torn read means the server is mid-write.
- A 401 was always treated as a stale port file, so a command spanning a server
  restart could delete the port file of a live server. Re-read the token first:
  the server publishes it before registering routes, so if it changed this is a
  restart and the request is simply retried.
Comment thread scopes/harmony/bit/server-commander.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8b472f6

A listener that inherited a stale port could answer the request with any JSON
and have it reported as the command's output, suppressing the real command.
bit-server always returns a { data, exitCode } object, so anything else now
fails safe: drop the port file and run the command in-process.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 19eb957

@davidfirst
davidfirst enabled auto-merge (squash) August 28, 2026 20:52
@davidfirst
davidfirst merged commit f291b38 into master Aug 28, 2026
14 checks passed
@davidfirst
davidfirst deleted the perf-cli-drop-lsof-port-validation branch August 28, 2026 22:11
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.

2 participants