You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds a single namespaced entry point for sidecar lifecycle in both the
TypeScript and Python SDKs:
pmxt.server.status() structured snapshot (running, pid, port, version, uptime)
pmxt.server.health() fast /health probe
pmxt.server.start() idempotent start
pmxt.server.stop() stop and clean up the lock file
pmxt.server.restart() stop + start
pmxt.server.logs(n) tail ~/.pmxt/server.log
pmxt-ensure-server now redirects spawned sidecar stdio to
~/.pmxt/server.log so logs() has something to read. Previously stdio
was dropped and boot crashes left no trace.
Also fixes a long-standing race in ServerManager.ensureServerRunning().
Creating multiple Exchange instances in parallel caused every request
to return 401 Unauthorized: each Exchange constructed its own
ServerManager, each one called ensureServerRunning() concurrently,
every call saw 'no server running', every call spawned its own sidecar
via pmxt-ensure-server, and the lock file ended up pointing at
whichever spawn wrote last. Each Exchange had already captured its
basePath at construction time, so most requests hit a sidecar whose
access token did NOT match the token later read from the lock file.
Fix is process-wide coalescing:
- TypeScript: static Promise<void> | null cache on ServerManager.
Concurrent callers await the same in-flight promise; the cache is
cleared on settle so later calls can re-check sidecar state.
- Python: class-level threading.Lock wrapping the entire
check-and-spawn critical section. The is-alive check is
re-evaluated inside the lock so threads that lose the race observe
the sidecar that the winning thread just started.
Fully backwards compatible: pmxt.stopServer() / pmxt.restartServer()
(and stop_server / restart_server in Python) remain first-class
aliases. No deprecation, no warnings.
Copy file name to clipboardExpand all lines: changelog.md
+33Lines changed: 33 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,39 @@
2
2
3
3
All notable changes to this project will be documented in this file.
4
4
5
+
## [2.26.0] - 2026-04-08
6
+
7
+
### Fixed
8
+
9
+
-**`ServerManager.ensureServerRunning()` race condition (TypeScript and Python SDKs)**: Creating multiple `Exchange` instances in parallel (e.g. `const p = new Polymarket(); const k = new Kalshi(); const l = new Limitless();`) caused every request to return `401 Unauthorized`. Each `Exchange` constructed its own `ServerManager` and each one called `ensureServerRunning()` concurrently. Every call saw "no server running", every call spawned its own sidecar via `pmxt-ensure-server`, and the lock file ended up pointing at whichever spawn wrote last — but each `Exchange` had already captured its own `basePath` at construction time, so most requests hit a sidecar whose access token did NOT match the token later read from the lock file.
10
+
11
+
Fix is process-wide coalescing inside `ServerManager`:
12
+
-**TypeScript**: `ensureServerRunning()` now uses a static `Promise | null` cache. Concurrent callers await the same in-flight promise; the cache is cleared on settle so later calls can re-check the sidecar state.
13
+
-**Python**: `ensure_server_running()` now holds a class-level `threading.Lock` for the entire check-and-spawn critical section. The "is the server already running?" check is re-evaluated inside the lock so threads that lose the race observe the sidecar that the winning thread just started.
14
+
15
+
### Added
16
+
17
+
-**`pmxt.server` namespace for sidecar lifecycle management** (TypeScript and Python SDKs): A single, discoverable namespace for managing the background sidecar. All six commands are available identically in both SDKs:
18
+
-`pmxt.server.status()` — Structured snapshot: `{ running, pid, port, version, uptimeSeconds, lockFile }`. Returns a fresh object on every call (no shared mutable state).
19
+
-`pmxt.server.health()` — Returns `true` if the sidecar responds to `/health`, `false` otherwise. Fast, no side effects.
20
+
-`pmxt.server.start()` — Idempotently starts the sidecar. No-op if one is already running.
21
+
-`pmxt.server.stop()` — Stops the sidecar and removes the lock file.
22
+
-`pmxt.server.restart()` — Stop + start.
23
+
-`pmxt.server.logs(n = 50)` — Returns the last `n` lines from `~/.pmxt/server.log`, or an empty list if the launcher never wrote a log file.
24
+
25
+
Motivation: sidecar lifecycle is a real surface users hit regularly — stale lock files, zombie sidecars from crashed parents, version mismatches, and race conditions when multiple `Exchange` instances boot in parallel. Previously users had to reach into `ServerManager` directly or shell out to `ps` / `lsof` to diagnose. `pmxt.server.*` makes the lifecycle observable and controllable from a single entry point. Example:
26
+
27
+
```typescript
28
+
importpmxtfrom'pmxtjs';
29
+
const s =awaitpmxt.server.status();
30
+
if (!s.running) awaitpmxt.server.start();
31
+
console.log(awaitpmxt.server.logs(20));
32
+
```
33
+
34
+
-**Sidecar writes stdout/stderr to `~/.pmxt/server.log`**: `pmxt-ensure-server` now redirects the spawned sidecar's stdio to a log file in the `~/.pmxt/` directory so `pmxt.server.logs()` has something to read. Previously stdio was dropped (`stdio: 'ignore'`) and any crash during boot left no trace.
35
+
36
+
Fully backwards compatible: the existing flat helpers `pmxt.stopServer()` / `pmxt.restartServer()` (TypeScript) and `pmxt.stop_server()` / `pmxt.restart_server()` (Python) remain first-class, fully-supported aliases for `pmxt.server.stop()` / `pmxt.server.restart()`. No deprecation, no warnings — both spellings work and will keep working.
Copy file name to clipboardExpand all lines: scripts/templates/api-reference.python.md.hbs
+57-7Lines changed: 57 additions & 7 deletions
Original file line number
Diff line number
Diff line change
@@ -29,24 +29,74 @@ print(markets[0].title)
29
29
30
30
## Server Management
31
31
32
-
The SDK provides global functions to manage the background sidecar server. This is useful for clearing state, resolving "port busy" errors, or ensuring a clean slate in interactive environments like Jupyter.
32
+
The SDK exposes a `pmxt.server` namespace for managing the background sidecar server. Use these commands for clearing state, resolving "port busy" errors, inspecting server health, or tailing logs from interactive environments like Jupyter.
Returns `True` if the sidecar's `/health` endpoint responds with status `ok`, otherwise `False`. Lighter than `status()` when you only need a boolean liveness check.
58
+
59
+
```python
60
+
import pmxt
61
+
if not pmxt.server.health():
62
+
pmxt.server.restart()
63
+
```
64
+
65
+
### `pmxt.server.start`
66
+
67
+
Idempotently start the sidecar. Returns immediately if a healthy server is already running. Use this when you want to fail fast on startup rather than letting the first API call lazily boot the server.
68
+
69
+
```python
70
+
import pmxt
71
+
pmxt.server.start()
72
+
```
73
+
74
+
### `pmxt.server.stop`
75
+
76
+
Stop the running sidecar and clean up its lock file.
77
+
78
+
```python
79
+
import pmxt
80
+
pmxt.server.stop()
81
+
```
82
+
83
+
### `pmxt.server.restart`
35
84
36
-
Stop the background PMXT sidecar server and clean up lock files.
85
+
Stop the sidecar (if running) and start a fresh one. Equivalent to `stop()` followed by `start()`.
37
86
38
87
```python
39
88
import pmxt
40
-
pmxt.stop_server()
89
+
pmxt.server.restart()
41
90
```
42
91
43
-
### `restart_server`
92
+
### `pmxt.server.logs`
44
93
45
-
Restart the background PMXT sidecar server. Equivalent to calling `stop_server()` followed by a fresh start.
94
+
Return the last `n` lines (default `50`) from the sidecar log file at `~/.pmxt/server.log`. Returns an empty list if no log file exists yet. Invaluable when the server is misbehaving and you need to see what it actually printed.
The SDK provides global functions to manage the background sidecar server. This is useful for clearing state or
34
-
resolving "port busy" errors.
33
+
The SDK exposes a `pmxt.server` namespace for managing the background sidecar server. Use these commands for clearing state, resolving "port busy" errors, inspecting health, or tailing logs.
Stop the background PMXT sidecar server and clean up lock files.
56
+
### `pmxt.server.health`
57
+
58
+
Returns `true` if the sidecar's `/health` endpoint responds with status `ok`, otherwise `false`. Lighter than `status()` when you only need a boolean liveness check.
39
59
40
60
```typescript
41
61
import pmxt from 'pmxtjs';
42
-
await pmxt.stopServer();
62
+
if (!(await pmxt.server.health())) {
63
+
await pmxt.server.restart();
64
+
}
43
65
```
44
66
45
-
### `restartServer`
67
+
### `pmxt.server.start`
46
68
47
-
Restart the background PMXT sidecar server. Equivalent to calling `stopServer()` followed by a fresh start.
69
+
Idempotently start the sidecar. Returns immediately if a healthy server is already running. Use this when you want to fail fast on startup rather than letting the first API call lazily boot the server.
48
70
49
71
```typescript
50
72
import pmxt from 'pmxtjs';
51
-
await pmxt.restartServer();
73
+
await pmxt.server.start();
74
+
```
75
+
76
+
### `pmxt.server.stop`
77
+
78
+
Stop the running sidecar and clean up its lock file.
79
+
80
+
```typescript
81
+
import pmxt from 'pmxtjs';
82
+
await pmxt.server.stop();
83
+
```
84
+
85
+
### `pmxt.server.restart`
86
+
87
+
Stop the sidecar (if running) and start a fresh one. Equivalent to `stop()` followed by `start()`.
88
+
89
+
```typescript
90
+
import pmxt from 'pmxtjs';
91
+
await pmxt.server.restart();
92
+
```
93
+
94
+
### `pmxt.server.logs`
95
+
96
+
Return the last `n` lines (default `50`) from the sidecar log file at `~/.pmxt/server.log`. Returns an empty array if no log file exists yet. Invaluable when the server is misbehaving and you need to see what it actually printed.
Copy file name to clipboardExpand all lines: sdks/python/API_REFERENCE.md
+65-7Lines changed: 65 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,24 +29,74 @@ print(markets[0].title)
29
29
30
30
## Server Management
31
31
32
-
The SDK provides global functions to manage the background sidecar server. This is useful for clearing state, resolving "port busy" errors, or ensuring a clean slate in interactive environments like Jupyter.
32
+
The SDK exposes a `pmxt.server` namespace for managing the background sidecar server. Use these commands for clearing state, resolving "port busy" errors, inspecting server health, or tailing logs from interactive environments like Jupyter.
Stop the background PMXT sidecar server and clean up lock files.
55
+
### `pmxt.server.health`
56
+
57
+
Returns `True` if the sidecar's `/health` endpoint responds with status `ok`, otherwise `False`. Lighter than `status()` when you only need a boolean liveness check.
37
58
38
59
```python
39
60
import pmxt
40
-
pmxt.stop_server()
61
+
ifnot pmxt.server.health():
62
+
pmxt.server.restart()
41
63
```
42
64
43
-
### `restart_server`
65
+
### `pmxt.server.start`
44
66
45
-
Restart the background PMXT sidecar server. Equivalent to calling `stop_server()` followed by a fresh start.
67
+
Idempotently start the sidecar. Returns immediately if a healthy server is already running. Use this when you want to fail fast on startup rather than letting the first API call lazily boot the server.
46
68
47
69
```python
48
70
import pmxt
49
-
pmxt.restart_server()
71
+
pmxt.server.start()
72
+
```
73
+
74
+
### `pmxt.server.stop`
75
+
76
+
Stop the running sidecar and clean up its lock file.
77
+
78
+
```python
79
+
import pmxt
80
+
pmxt.server.stop()
81
+
```
82
+
83
+
### `pmxt.server.restart`
84
+
85
+
Stop the sidecar (if running) and start a fresh one. Equivalent to `stop()` followed by `start()`.
86
+
87
+
```python
88
+
import pmxt
89
+
pmxt.server.restart()
90
+
```
91
+
92
+
### `pmxt.server.logs`
93
+
94
+
Return the last `n` lines (default `50`) from the sidecar log file at `~/.pmxt/server.log`. Returns an empty list if no log file exists yet. Invaluable when the server is misbehaving and you need to see what it actually printed.
95
+
96
+
```python
97
+
import pmxt
98
+
for line in pmxt.server.logs(100):
99
+
print(line)
50
100
```
51
101
52
102
---
@@ -1144,6 +1194,7 @@ class UnifiedMarket:
1144
1194
market_id: str# The unique identifier for this market
0 commit comments