Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) by argszero · Pull Request #841 · argszero/emrg · GitHub
Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)
Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.
Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
file — if a live daemon already answers, refuse to start a duplicate.
Stale-restart sequences can leave the pid file missing while an old
daemon is still alive; the pid check alone then lets a second instance
bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
the port file is removed. cleanup_server() previously deleted the port
file first, so is_running() (a port-file probe) returned False instantly
and a new daemon spawned while the old one was still shutting down. Now
waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
then cleanup_server() only after death.
Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

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

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into masterAug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh
Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
Sign up for freeto 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.

1 participant

@argszero