daslang-live: --live-port flag + port-keyed lock + .das resolution chain#2726
Merged
Conversation
Resolves the "two daslang trees can't both run daslang-live" interference
that the parallel-checkout plan surfaced. After this:
daslang-live my.das --live-port 19090 # binary form
daslang script.das -- --live-port 19090 # scripts using live_api
coexist with another instance on the default 9090.
Port precedence (highest first), unified across C++ binary and .das module
via an env-var roundtrip so the single-instance lock and the HTTP bind
can never disagree:
1. live_api_set_port(p) (programmatic, always wins)
2. script argv --live-port N (find_flag_raw_value, full argv)
3. env DASLANG_LIVE_PORT (daslang-live re-exports its
resolved port into this env)
4. get_das_root() /daslang-live.cfg.json "port" key
5. default 9090
C++ daslang-live binary parses --live-port pre-doubledash, writes the
resolved port back into DASLANG_LIVE_PORT, and keys acquire_single_instance()
on that port (Windows: daslang-live-single-instance-<port> mutex; POSIX:
/tmp/daslang-live-<port>.lock).
MCP do_live_launch now forwards its port arg to the spawned binary --
fixes the deferred-test root cause documented in the mouse-card
daslang-live-no-port-flag-single-instance-lock-e2e-spawn-tests-infeasible.md
(now marked RESOLVED).
Tests cover parse_port_string boundaries, --live-port and --live-port=N
forms, JSON config parsing, precedence chains, and the
live_api_set_port-after-init smoke. 78/78 pass interpreted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enhances daslang-live port configurability and enables multiple concurrent instances by keying the single-instance lock to the resolved REST API port, while keeping the C++ launcher and daScript live_api module in sync via DASLANG_LIVE_PORT.
Changes:
- Added
--live-port <N>handling indaslang-live, plus port-keyed single-instance lock and env handoff (DASLANG_LIVE_PORT). - Implemented a testable 5-level port resolution chain in
live/live_api(argv/env/config/default + programmatic override). - Updated MCP live launch tooling to forward the requested port to the spawned
daslang-live, and added dedicated tests + docs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/mcp/tools/live.das | Forwards --live-port to spawned daslang-live to align bind port with MCP polling port. |
| utils/daslang-live/main.cpp | Adds --live-port parsing, resolves/export port via env, and keys single-instance lock by port. |
| modules/dasLiveHost/live/live_api.das | Adds pure parsing/precedence helpers + init-time override resolution from argv/env/config. |
| tests/live_host/test_port_override.das | New test suite covering parsing and precedence + setter-after-init behavior. |
| skills/daslang_live.md | Documents precedence chain and port-keyed locking behavior. |
| mouse-data/docs/daslang-live-no-port-flag-single-instance-lock-e2e-spawn-tests-infeasible.md | Marks prior limitation as resolved and preserves historical details. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…nly guard Per Copilot review on #2726: - parse_port_strict() in daslang-live binary replaces atoi for both --live-port arg and DASLANG_LIVE_PORT env. atoi silently accepted '9090abc' -> 9090; the new parser rejects trailing garbage to match the .das side's try_to_int. - Accept --live-port=N alongside --live-port N for parity with find_flag_raw_value on the .das side. - do_live_launch now rejects non-digit / over-5-char port at entry so a caller-supplied port cannot smuggle quotes / spaces / metacharacters into the shell-ready argv string passed to system(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI darwin Release failure (run 26078968013): test_aot reported error[50101] AOT link failed on every live_api function referenced by tests/live_host/test_port_override.das (the first AOT test to require live/live_api). Root cause: live_api.das was missing from AOT_LIVE_HOST_MODULE_FILES in tests/aot/CMakeLists.txt. Without an AOT-cpp build of live_api itself, the test_port_override.das.cpp recorded hashes that disagreed with what test_aot.exe computed at simulate time -> link miss on live_api_set_port / parse_port_string / parse_argv_port / etc. Pre-existing live_host tests only require live_host (the C++ DLL bridge), so the gap was invisible until now. Fix: add modules/dasLiveHost/live/live_api.das to the list, same spot as live_api_builtins / live_api_stdio. Local reconfigure generates test_aot_live_host_modules_live_api.das.cpp under modules/dasLiveHost/live/_aot_generated/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
utils/mcp/tools/live.das:156
- Now that
live_apisupports env/config-based port overrides, the MCP tool’s defaulting logic (empty(port) ? "9090" : port) can become incorrect: if a user relies onDASLANG_LIVE_PORTordaslang-live.cfg.jsonwithout passing an explicitportto MCP,do_live_launch/live_is_runningwill still poll 9090 and likely time out even though the server is up on the overridden port. Consider resolving the default port in the MCP tool from the same sources (env and/or config) whenportis empty, so polling/launch align with the actual bind port.
let p = empty(port) ? "9090" : port
// Check if already running
if (live_is_running(p)) {
// Already running — get status to show useful info
Per Boris's direction on PR #2726 R2: keep daslang-live stateless -- drop the env-var roundtrip and the config-file fallback. Both sides (C++ daslang-live + .das live_api) now scan the same source (full argv, including post-doubledash) for --live-port, so the lock key and the HTTP bind cannot drift. Correctness bugs fixed (each with a repro test per the "correctness-bug -> repro test" rule): - Thread 4: MCP do_live_launch's port_is_safe accepted "0", "65536", "99999" -- daslang-live would error after 10s of polling. Renamed to port_in_range and now actually parses + range-checks [1, 65535]. - Thread 5: C++ arg loop stopped at doubledash while .das scanned full argv, so `daslang-live foo.das -- --live-port 19090` would bind 19090 on the .das side but key the C++ lock on 9090. New find_live_port_in_argv() scans the full argv. parse_argv_port test pins the post-doubledash case on the .das side. - Thread 8: env-roundtrip masked the config-file fallthrough. Resolved by removing both -- no more env, no more config file. Doc-only (Threads 6, 7): live_api_set_port "always wins after init" was misleading. Server binds at LiveApiAgent constructor time and does not rebind. Reworded the docstring on live_api_set_port and the [init] function comment to say "before agent constructs". RST audit (Boris's "MCP server and live documentation might be out of sync" request): - mcp.rst: project_root was referenced 18+ times in protocol.das but documented 0 times. Added a Common parameters subsection listing project + project_root once for all tools that accept them. - mcp.rst: Live-reload control table rewritten with per-tool arg surface (file/project/project_root/port on live_launch; full on live_reload; paused on live_pause; name/args on live_command; commands on live_commands). - daslang_live.rst: CLI reference filled in to match main.cpp (-project_root, -v1syntax, -track-allocations, -heap-report, --no-dyn-modules, --no-dump-leaks, --live-port, -h/--help). - daslang_live.rst: single-instance lock note now mentions port-keying (daslang-live-single-instance-<port> / /tmp/daslang-live-<port>.lock). - daslang_live.rst: REST API section documents the 3-level precedence chain (programmatic > argv > default) and the "before constructor" caveat on live_api_set_port. skills/daslang_live.md updated to match the simplified chain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Copilot review on PR #2726 R3: - Thread 9 (real correctness bug): C++ find_live_port_in_argv kept 'last VALID', while .das clargs.find_flag_raw_value keeps 'last RAW (validated to 0 on bad input)'. A tail like '--live-port 19090 --live-port abc' would have C++ key its lock on 19090 while .das defaulted to 9090 -- lock-vs-bind drift. Dropped the if(p>0) guard so the C++ scan is also last-occurrence-wins-unconditional. Repro test added per the correctness-bug rule: parse_argv_port treating invalid trailing values as 0. - Thread 10 (doc fix): g_resolvedLivePort comment still mentioned a 'DASLANG_LIVE_PORT env handoff' that was removed in cf58023. Reworded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
daslang-liveaccepts--live-port <N>(or--live-port=N), recognized BOTH before and after--. The host-wide single-instance lock is keyed on the resolved port (daslang-live-single-instance-<port>mutex on Windows //tmp/daslang-live-<port>.lockon POSIX), so two binaries on different ports coexist on the same host; same port still serializes.live_apimodule resolves the bind port from the same full argv viafind_flag_raw_value. Both sides agree because they read identical input — no env var, no config file, no state.do_live_launchforwards itsportarg to the spawned binary as--live-port {port}and rejects values outside[1, 65535](digit-only + parsed range check viaport_in_range) before composing the spawn argv.daslang-livesimultaneously on different ports.live_api.daswas missing fromtests/aot/CMakeLists.txt'sAOT_LIVE_HOST_MODULE_FILES, so any AOT test requiringlive/live_api50101'd. Added.Port precedence (highest first, both sides)
live_api_set_port(p)called BEFORE theLiveApiAgentconstructor. The server binds at construction time and never rebinds; later setter calls only mutate the global, not the bound socket.--live-port Nor--live-port=Nanywhere inget_command_line_arguments(), including the post---slice. Last occurrence wins (clargs convention), invalid trailing values coerce to 0 so both sides fall through identically.9090.The C++ daslang-live binary scans the same full argv with the same strict parse semantics and keys its lock on the resolved port.
Files
utils/daslang-live/main.cpp--live-port/--live-port=Nparsing viafind_live_port_in_argv(full argv, last-occurrence-wins, strict parse); port-keyed single-instance lock; help text. No env, no config-file.modules/dasLiveHost/live/live_api.dasparse_port_string/parse_argv_port/resolve_port_override_from(pure, testable) +resolve_port_override+[init]. Pulls indaslib/clargsforfind_flag_raw_valueanddaslib/strings_convertfortry_to_int.utils/mcp/tools/live.dasdo_live_launchprepends--live-port {port}to the spawn argv when caller passedport;port_in_rangerejects non-numeric / out-of-range up front (avoids 10s MCP timeout).tests/live_host/test_port_override.das--argv, last-occurrence-invalid).tests/aot/CMakeLists.txtmodules/dasLiveHost/live/live_api.dasas an AOT module-file so test_aot links its AOT cpp (was the darwin Release 50101 root cause).skills/daslang_live.mddoc/source/reference/utils/daslang_live.rst-project_root,-v1syntax,-track-allocations,-heap-report,--no-dyn-modules,--no-dump-leaks,--live-port,-h/--help); precedence chain documented; lock-key note updated.doc/source/reference/utils/mcp.rstproject+project_root(project_rootwas used by 18+ tools but documented 0 times). Live-reload control table rewritten with per-tool arg surface.mouse-data/docs/daslang-live-no-port-flag-...Test plan
daslang.exe dastest/dastest.das -- --test tests/live_host— interpreted: 75/75 pass (including 21 new port-override cases)..dasfiles viautils/mcp/subtools/lint_tool.das.daslib/das_source_formatter_fio).tests/live_host/test_port_override.dassucceeds locally; fulltest_aot.exeruns on CI (darwin Release green after the CMake registration fix).daslang-live foo.das --live-port 9090abc→ strict-parse rejection, exits with error;daslang-live foo.das --live-port=19090parsed successfully; help text shows the flag.--live-port 9090+--live-port 19090) — to do post-merge.🤖 Generated with Claude Code