fix(cookbook): only block model launch on real port collisions (#4760)

* Fix #4507: only block model launch on real port collisions

Quick-run hardcoded port 8000 and never called _nextAvailablePort(), so
every launch collided. Both pre-launch guards (serve panel + quick-run)
were count-based and fired regardless of port.

- quick-run now auto-assigns a free port (8080 for llama.cpp)
- both guards parse the new port and only prompt on a real overlap,
  stopping only the colliding serve
- dialog reports the actual port instead of a hardcoded 8000

* refactor(cookbook): share _taskPort for port parsing; auto-assign llama.cpp port

Addresses review on #4760:
- _taskPort regex now matches --port= as well as --port (space)
- _nextAvailablePort and both launch guards reuse _taskPort instead of inline regex
- quick-run llama.cpp no longer pins 8080, so two can run concurrently

* fix(cookbook): _taskPort also parses -p; add port-parsing tests

Addresses review on #4760:
- _taskPort now matches -p <n> too, so it's the complete single reader
  (was missing the short flag that other readers already handle)
- add tests/test_cookbook_port_parsing_js.py covering the port forms,
  shared-reader reuse, and llama.cpp auto-assign

* test(cookbook): extract pure port helpers and test behavior

Addresses review on #4760: the prior tests only asserted source strings.
- extract portOf() and nextFreePort() into static/js/cookbookPorts.js
- cookbookRunning.js imports them; _taskPort and _nextAvailablePort delegate
- tests run the helpers via node and assert real behavior: all port forms
  (--port, --port=, -p, -p=), next-free-port skipping taken ports, and the
  same-port-clash / different-port-coexist outcome

---------

Co-authored-by: samy <samy@odysseus.boukouro.com>
This commit is contained in:
Samy
2026-06-24 13:44:09 -04:00
committed by GitHub
parent 22379fe736
commit 5d23495eb2
5 changed files with 130 additions and 56 deletions
+6 -9
View File
@@ -8,6 +8,7 @@ import uiModule from './ui.js';
import { _diagnose, _showDiagnosis, _clearDiagnosis } from './cookbook-diagnosis.js';
import { registerMenuDismiss } from './escMenuStack.js';
import { computeProgressSignal } from './cookbookProgressSignal.js';
import { portOf, nextFreePort } from './cookbookPorts.js';
// Human-friendly badge label for a task's internal status. Avoids surfacing
// the word "error" in the sidebar — a server the user stopped or one that
@@ -266,9 +267,7 @@ function _taskHostLabel(task) {
}
function _taskPort(task) {
const cmd = task?.payload?._cmd || '';
const match = cmd.match(/--port\s+(\d+)/);
return match ? match[1] : '';
return portOf(task?.payload?._cmd || '');
}
function _buildCrashReport(task, outputText) {
@@ -455,16 +454,14 @@ function _nextAvailablePort() {
const usedPorts = new Set();
tasks.forEach(t => {
if (t.type === 'serve' && (t.status === 'running' || t.status === 'queued')) {
const m = t.payload?._cmd?.match(/--port\s+(\d+)/);
if (m) usedPorts.add(parseInt(m[1]));
const p = _taskPort(t);
if (p) usedPorts.add(parseInt(p));
}
});
presets.forEach(p => {
if (p.port) usedPorts.add(parseInt(p.port));
});
let port = 8000;
while (usedPorts.has(port)) port++;
return String(port);
return nextFreePort(usedPorts);
}
// ── Endpoint cleanup ──
@@ -3987,4 +3984,4 @@ export function initRunning(shared) {
}
// Also export _retryDownload and _nextAvailablePort for use by other modules
export { _retryDownload, _nextAvailablePort, _processQueue };
export { _retryDownload, _nextAvailablePort, _processQueue, _taskPort };