Skip to content

Commit 1e91038

Browse files
committed
fix: handle active remote Codex writers
1 parent a9547f4 commit 1e91038

4 files changed

Lines changed: 63 additions & 4 deletions

File tree

Integrations/noturcode-agent.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import json
99
import os
1010
import pathlib
11+
import re
1112
import shutil
1213
import socket
1314
import stat
15+
import subprocess
1416
import sys
1517
import uuid
1618

@@ -354,6 +356,42 @@ def doctor() -> int:
354356
return 0 if paired else 1
355357

356358

359+
def active_codex_session_ids(process_list: str) -> list[str]:
360+
identifiers: list[str] = []
361+
for match in re.finditer(r"\bcodex\s+resume\s+([0-9a-fA-F-]{36})\b", process_list):
362+
identifier = match.group(1).lower()
363+
if identifier not in identifiers:
364+
identifiers.append(identifier)
365+
return identifiers
366+
367+
368+
def resume_codex() -> int:
369+
process_scan = subprocess.run(
370+
["pgrep", "-af", "codex"],
371+
check=False,
372+
capture_output=True,
373+
text=True,
374+
)
375+
active = active_codex_session_ids(process_scan.stdout)
376+
if active:
377+
print("", file=sys.stderr)
378+
print("[!] Codex chats with an active writer:", file=sys.stderr)
379+
for identifier in active:
380+
print(f" {identifier}", file=sys.stderr)
381+
print(" Close one in its old terminal before you select it here.", file=sys.stderr)
382+
print("", file=sys.stderr)
383+
try:
384+
result = subprocess.call(["codex", "resume", "--all"])
385+
except FileNotFoundError:
386+
print("Noturcode: Codex is not installed on this VPS.", file=sys.stderr)
387+
return 127
388+
if result != 0:
389+
print("", file=sys.stderr)
390+
print("Noturcode: The chat was not resumed.", file=sys.stderr)
391+
print("Choose another chat, or close its active writer first.", file=sys.stderr)
392+
return result
393+
394+
357395
def build_parser() -> argparse.ArgumentParser:
358396
parser = argparse.ArgumentParser(prog="noturcode-agent")
359397
commands = parser.add_subparsers(dest="command", required=True)
@@ -363,6 +401,7 @@ def build_parser() -> argparse.ArgumentParser:
363401
hook_parser.add_argument("--source", required=True, choices=("claude", "codex", "gemini"))
364402
commands.add_parser("install")
365403
commands.add_parser("doctor")
404+
commands.add_parser("resume")
366405
proxy_parser = commands.add_parser("proxy")
367406
proxy_parser.add_argument("--listen", required=True)
368407
proxy_parser.add_argument("--target", required=True)
@@ -380,6 +419,8 @@ def main() -> int:
380419
return install_hooks()
381420
if arguments.command == "doctor":
382421
return doctor()
422+
if arguments.command == "resume":
423+
return resume_codex()
383424
if arguments.command == "proxy":
384425
return serve_proxy(arguments.listen, arguments.target, arguments.max_connections)
385426
return 2

Integrations/noturcode-cli.zsh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ connect_host() {
174174
terminal_id=$("$bridge" terminal-id) || return 1
175175
remote_socket="/tmp/noturcode-${USER//[^A-Za-z0-9_-]/_}-$$.sock"
176176
if [[ "$mode" == "resume" ]]; then
177-
remote_command="export NOTURCODE_REMOTE_SOCKET='$remote_socket'; export NOTURCODE_TERMINAL_SESSION_ID='$terminal_id'; export NOTURCODE_REMOTE_HOST='$host'; export NOTURCODE_SESSION_NAME='$chat_name'; exec \"\${SHELL:-/bin/sh}\" -lc 'codex resume --all'"
177+
remote_command="export NOTURCODE_REMOTE_SOCKET='$remote_socket'; export NOTURCODE_TERMINAL_SESSION_ID='$terminal_id'; export NOTURCODE_REMOTE_HOST='$host'; export NOTURCODE_SESSION_NAME='$chat_name'; \"\$HOME/.local/bin/noturcode-agent\" resume; resume_exit=\$?; if [ \$resume_exit -ne 0 ]; then print '\nNoturcode kept this VPS shell open. Run nc resume again after the active chat closes.' 2>/dev/null || echo '\nNoturcode kept this VPS shell open. Run nc resume again after the active chat closes.'; exec \"\${SHELL:-/bin/sh}\" -l; fi"
178178
else
179179
remote_command="export NOTURCODE_REMOTE_SOCKET='$remote_socket'; export NOTURCODE_TERMINAL_SESSION_ID='$terminal_id'; export NOTURCODE_REMOTE_HOST='$host'; export NOTURCODE_SESSION_NAME='$chat_name'; exec \"\${SHELL:-/bin/sh}\" -l"
180180
fi
@@ -194,9 +194,9 @@ connect_host() {
194194
-o StreamLocalBindUnlink=yes \
195195
-R "${remote_socket}:${active_proxy_socket}" \
196196
"$host" "$remote_command"
197-
local status=$?
197+
local ssh_exit_code=$?
198198
stop_proxy
199-
return "$status"
199+
return "$ssh_exit_code"
200200
}
201201

202202
resume_codex() {

Tests/NoturcodeCoreTests/NoturcodeCoreTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,10 @@ final class NoturcodeCoreTests: XCTestCase {
342342
XCTAssertTrue(cli.contains("Open an SSH workspace"))
343343
XCTAssertTrue(cli.contains("Resume an existing Codex chat"))
344344
XCTAssertTrue(cli.contains("Chat name"))
345-
XCTAssertTrue(cli.contains("codex resume --all"))
345+
XCTAssertTrue(agent.contains("codex\", \"resume\", \"--all"))
346+
XCTAssertTrue(cli.contains("noturcode-agent\\\" resume"))
347+
XCTAssertTrue(cli.contains("ssh_exit_code"))
348+
XCTAssertFalse(cli.contains("local status="))
346349
XCTAssertTrue(cli.contains("settle()"))
347350
XCTAssertTrue(cli.contains("[===>]"))
348351
XCTAssertTrue(cli.contains("StreamLocalBindUnlink=yes"))

Tests/RemoteAgentTests/test_remote_agent.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ def test_local_proxy_frames_ssh_request_before_forwarding_to_app(self):
172172
self.assertEqual(response["error"], "invalid code")
173173
self.assertEqual(app_server.request["type"], "remotePair")
174174

175+
def test_active_codex_session_ids_extracts_unique_resume_writers(self):
176+
process_list = """
177+
100 node /usr/bin/codex resume 019ff72e-75b9-7700-aa47-f0295f58b30c
178+
101 /usr/bin/codex resume 019ff72e-75b9-7700-aa47-f0295f58b30c
179+
102 /usr/bin/codex resume 11111111-2222-3333-4444-555555555555
180+
103 /usr/bin/codex-code-mode-host
181+
"""
182+
self.assertEqual(
183+
self.agent.active_codex_session_ids(process_list),
184+
[
185+
"019ff72e-75b9-7700-aa47-f0295f58b30c",
186+
"11111111-2222-3333-4444-555555555555",
187+
],
188+
)
189+
175190

176191
if __name__ == "__main__":
177192
unittest.main()

0 commit comments

Comments
 (0)