elevate-adapter: direct CreateSession for token refresh

Replace subprocess-based token refresh (superpowers run-session --child-session)
with direct SEQPACKET CreateSession RPC to superpowerd. This fixes:

1. Session PID mismatch: old approach registered session to short-lived sh
   subprocess PID, causing immediate pidAlive() failure on next use
2. Child session failure: --child-session fails when superpowerd restarts
   (parent session gone)
3. Gentoo python-exec2c exec chain: replace os.execvp startup with direct
   CreateSession call

The adapter now owns its session directly (peerPID = adapter PID), so the
session stays valid for the adapters entire lifetime.
This commit is contained in:
Ollie Agent 2026-07-21 07:42:54 +02:00
parent a65178f936
commit 058d7aed0d
1 changed files with 66 additions and 16 deletions

View File

@ -29,7 +29,6 @@ import os
import signal
import socket
import struct
import subprocess
import threading
_token_lock = threading.Lock()
@ -40,27 +39,74 @@ def _log(msg):
print(f"{_LOG_PREFIX}: {msg}", file=sys.stderr, flush=True)
def _read_master_token():
"""Read the superpowerd master token from the standard location."""
path = os.environ.get("SUPERPOWERD_MASTER_TOKEN_FILE") or \
os.path.join(os.path.expanduser("~"), "Private", "superpowerd_master.token")
try:
return open(path).read().strip()
except OSError as e:
_log(f"cannot read master token at {path}: {e}")
return None
def refresh_token():
"""Obtain a fresh session token from superpowerd via run-session.
"""Create a fresh session directly on superpowerd via SEQPACKET.
Connects to superpowerd and calls CreateSession with the master token.
Because WE are the connecting process, the session PID = our PID,
so pidAlive() stays true for our entire lifetime.
Returns the new token string, or None on failure.
Updates os.environ so subsequent calls use the fresh token.
"""
_log("refreshing session token")
_log("refreshing session token via direct CreateSession")
master_token = _read_master_token()
if not master_token:
_log("refresh failed: cannot read master token")
return None
sp_path = sp_socket_path()
try:
result = subprocess.run(
["superpowers", "run-session", "--child-session", "--",
"sh", "-c", "echo $SUPERPOWERD_SESSION_TOKEN"],
capture_output=True, text=True, timeout=10,
)
token = result.stdout.strip()
if token and result.returncode == 0:
sp = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
sp.connect(sp_path)
except OSError as e:
_log(f"refresh failed: connect {sp_path}: {e}")
return None
try:
req = json.dumps({
"jsonrpc": "2.0",
"method": "CreateSession",
"id": 1,
"params": {
"auth": {"master_token": master_token},
"env": dict(os.environ),
"working_dir": os.getcwd(),
},
}).encode()
sp.send(req)
resp_data = sp.recv(1024 * 1024)
sp.close()
resp = json.loads(resp_data)
if "error" in resp and resp["error"]:
msg = resp["error"].get("message", str(resp["error"]))
_log(f"refresh failed: superpowerd error: {msg}")
return None
token = resp.get("result", {}).get("token", "")
if token:
os.environ["SUPERPOWERD_SESSION_TOKEN"] = token
_log("session token refreshed successfully")
return token
_log(f"token refresh returned exit={result.returncode} stdout={result.stdout!r:.80}")
except (subprocess.TimeoutExpired, OSError) as e:
_log(f"token refresh failed: {e}")
_log(f"refresh failed: no token in response: {resp_data!r:.200}")
except (OSError, json.JSONDecodeError, ValueError) as e:
_log(f"refresh failed: {e}")
try:
sp.close()
except OSError:
pass
return None
@ -101,7 +147,7 @@ def _is_auth_error(sp_data):
err = resp.get("error")
if isinstance(err, dict):
msg = err.get("message", "").lower()
return "invalid session token" in msg or "session not found" in msg
return "invalid session token" in msg or "session not found" in msg or "session expired" in msg or "parent session expired" in msg
except (json.JSONDecodeError, ValueError):
pass
return False
@ -302,8 +348,12 @@ def _finish_response(conn, sp_data):
def main():
if "SUPERPOWERD_SESSION_TOKEN" not in os.environ:
os.execvp("superpowers", ["superpowers", "run-session", "--",
sys.executable, __file__] + sys.argv[1:])
# Get a token directly from superpowerd instead of os.execvp through
# superpowers run-session (which fails on Gentoo's python-exec2c).
token = refresh_token()
if not token:
_log("fatal: cannot obtain session token")
sys.exit(1)
sock_path = os.environ.get("OLLIE_ELEVATE_SOCKET") or default_socket_path()
sock_path = os.path.expanduser(sock_path)