relay-codex 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,320 @@
1
+ """Regression cases reproduced by the audit. No real accounts or model calls."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import io
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import tempfile
10
+ import unittest
11
+ from unittest.mock import patch
12
+
13
+ from router import Controller, RemoteError, parse_arguments, resolve_resume_target
14
+ from test_router import FakeAccount, FakeBackend, FakeClient, completed
15
+ from test_pool import Clock
16
+ from pool import Pool, process_birth
17
+ from settings import atomic_json
18
+ import commands
19
+ import service
20
+
21
+
22
+ class RecoveryRegressions(unittest.IsolatedAsyncioTestCase):
23
+ async def test_late_failure_cannot_bypass_review_hold(self):
24
+ with tempfile.TemporaryDirectory() as folder:
25
+ clock = Clock()
26
+ pool = Pool(Path(folder), clock=clock)
27
+ backend = FakeBackend()
28
+ c = Controller(
29
+ [FakeAccount("one")],
30
+ backend,
31
+ pool=pool,
32
+ clock=clock,
33
+ sleep=clock.sleep,
34
+ report=lambda _: None,
35
+ )
36
+ c.client = FakeClient()
37
+ c.last_turn["thread-1"] = {"threadId": "thread-1"}
38
+ c.hold_for_review("thread-1")
39
+ await c.event(completed(code="policyCapacityUnavailable"))
40
+ await c.recovery
41
+ self.assertFalse(any(method == "turn/start" for method, _ in backend.calls))
42
+ self.assertTrue(pool.intent("thread-1")["review_required"])
43
+ await c.control("retry", {"thread": "thread-1"})
44
+ await c.recovery
45
+ self.assertTrue(any(method == "turn/start" for method, _ in backend.calls))
46
+ pool.close()
47
+
48
+ async def test_picker_permission_control_applies_before_thread_selection(self):
49
+ c = Controller([FakeAccount("one")], FakeBackend(), report=lambda _: None)
50
+ c.launch_overrides = {
51
+ "sandbox": "danger-full-access",
52
+ "approvalPolicy": "never",
53
+ }
54
+ await c.control(
55
+ "permissions", {"sandbox": "read-only", "approvalPolicy": "on-request"}
56
+ )
57
+ self.assertEqual(
58
+ c.launch_overrides, {"sandbox": "read-only", "approvalPolicy": "on-request"}
59
+ )
60
+ c.client = FakeClient()
61
+ await c.forward({"id": 1, "method": "thread/start", "params": {}}, {})
62
+ self.assertEqual(c.backend.calls[-1][1]["sandbox"], "read-only")
63
+
64
+ async def test_more_than_one_thousand_overloads_do_not_grow_call_stack(self):
65
+ clock = Clock()
66
+ backend = FakeBackend()
67
+ rejections = 1100
68
+ original = backend.call
69
+
70
+ async def overloaded(method, params=None, **kw):
71
+ nonlocal rejections
72
+ if method == "turn/start" and rejections:
73
+ rejections -= 1
74
+ raise RemoteError({"code": -32001})
75
+ return await original(method, params, **kw)
76
+
77
+ backend.call = overloaded
78
+ c = Controller(
79
+ [FakeAccount("one"), FakeAccount("two")],
80
+ backend,
81
+ clock=clock,
82
+ sleep=clock.sleep,
83
+ report=lambda _: None,
84
+ )
85
+ c.client = FakeClient()
86
+ c.last_turn["thread-1"] = {"threadId": "thread-1"}
87
+ await c.recover("thread-1", 0, "transient")
88
+ self.assertEqual(rejections, 0)
89
+ self.assertTrue(any(method == "turn/start" for method, _ in backend.calls))
90
+ self.assertFalse(c.paused_uncertain)
91
+
92
+ async def test_resume_lookup_uses_native_filtering_and_names(self):
93
+ class Backend:
94
+ def __init__(self):
95
+ self.calls = []
96
+
97
+ async def call(self, method, params):
98
+ self.calls.append((method, dict(params)))
99
+ return {
100
+ "data": [{"id": "interactive", "name": "named", "cwd": "/project"}]
101
+ }
102
+
103
+ backend = Backend()
104
+ selected = await resolve_resume_target(
105
+ ["resume", "--last"], Path("/home"), "/project", backend=backend
106
+ )
107
+ self.assertEqual(selected, "interactive")
108
+ self.assertNotIn("sourceKinds", backend.calls[-1][1])
109
+ self.assertEqual(backend.calls[-1][1]["cwd"], "/project")
110
+ selected = await resolve_resume_target(
111
+ ["resume", "named", "--all", "--include-non-interactive"],
112
+ Path("/home"),
113
+ "/project",
114
+ backend=backend,
115
+ )
116
+ self.assertEqual(selected, "interactive")
117
+ self.assertIn("exec", backend.calls[-1][1]["sourceKinds"])
118
+ self.assertNotIn("cwd", backend.calls[-1][1])
119
+
120
+ async def test_watch_ignores_heartbeats_and_redraws_only_meaningful_changes(self):
121
+ args = type(
122
+ "Args", (), {"watch": True, "json": True, "all": False, "interval": 30}
123
+ )()
124
+ count = 0
125
+ values = [
126
+ {"at": 1, "sessions": [{"id": "a", "seen": 1, "state": "idle"}]},
127
+ {"at": 2, "sessions": [{"id": "a", "seen": 2, "state": "idle"}]},
128
+ {"at": 3, "sessions": [{"id": "a", "seen": 3, "state": "working"}]},
129
+ ]
130
+
131
+ class Watch:
132
+ def __init__(self, state):
133
+ pass
134
+
135
+ async def changed(self, timeout):
136
+ nonlocal count
137
+ count += 1
138
+ if count == 3:
139
+ raise asyncio.CancelledError()
140
+
141
+ def close(self):
142
+ pass
143
+
144
+ output = io.StringIO()
145
+ with (
146
+ patch("watch.LocalChanges", Watch),
147
+ patch("commands.snapshot", side_effect=values),
148
+ contextlib.redirect_stdout(output),
149
+ ):
150
+ with self.assertRaises(asyncio.CancelledError):
151
+ await commands.status(args, Path("/unused"))
152
+ self.assertEqual(len(output.getvalue().splitlines()), 2)
153
+
154
+ async def test_control_wait_reports_rejection(self):
155
+ with tempfile.TemporaryDirectory() as folder:
156
+ state = Path(folder)
157
+ pool = Pool(state)
158
+ pool.session = "worker"
159
+ pool.assign("one")
160
+ args = parse_arguments(["pause", "worker", "--wait"])
161
+
162
+ async def reject(pool, ident, timeout):
163
+ raise ValueError("rejected")
164
+
165
+ with patch("service.wait_control", reject):
166
+ with self.assertRaisesRegex(ValueError, "rejected"):
167
+ await commands.control(args, state)
168
+ pool.close()
169
+
170
+
171
+ class WorkerRegressions(unittest.IsolatedAsyncioTestCase):
172
+ async def asyncSetUp(self):
173
+ from test_services import ServiceTests
174
+
175
+ self.case = ServiceTests("test_background_work_completes_without_frontend")
176
+ await self.case.asyncSetUp()
177
+
178
+ async def asyncTearDown(self):
179
+ await self.case.asyncTearDown()
180
+
181
+ async def test_worker_restores_reviewer_and_workspace_roots(self):
182
+ from test_services import wait_for
183
+ from router import RPC
184
+
185
+ c = self.case
186
+ await wait_for(lambda: json.loads(c.job.read_text()).get("resume"))
187
+ await wait_for(
188
+ lambda: any(
189
+ t["turns"] and t["turns"][-1]["status"] == "completed"
190
+ for t in json.loads((c.home / "fake-history.json").read_text())[
191
+ "threads"
192
+ ].values()
193
+ )
194
+ )
195
+ thread = json.loads(c.job.read_text())["resume"]
196
+ c.task.cancel()
197
+ await asyncio.gather(c.task, return_exceptions=True)
198
+ pool = Pool(c.state)
199
+ pool.session = c.ident
200
+ pool.intent(
201
+ thread,
202
+ {
203
+ "role": "foreground",
204
+ "accepted": True,
205
+ "state": "completed",
206
+ "settings": {
207
+ "approvalsReviewer": "auto_review",
208
+ "runtimeWorkspaceRoots": [str(c.home), "/private/tmp/audit-extra"],
209
+ },
210
+ },
211
+ )
212
+ observed = []
213
+ original = RPC.call
214
+
215
+ async def capture(rpc, method, params=None, **kw):
216
+ if method == "thread/resume":
217
+ observed.append(params)
218
+ return await original(rpc, method, params, **kw)
219
+
220
+ with patch.object(RPC, "call", capture):
221
+ c.task = asyncio.create_task(service.serve(c.state, c.ident, c.binary))
222
+ await wait_for(c.meta.exists)
223
+ self.assertEqual(observed[0]["approvalsReviewer"], "auto_review")
224
+ self.assertIn("/private/tmp/audit-extra", observed[0]["runtimeWorkspaceRoots"])
225
+ pool.close()
226
+
227
+ async def test_project_selection_updates_restart_and_attach_metadata(self):
228
+ from websockets.asyncio.client import connect
229
+
230
+ c = self.case
231
+ other = c.home / "other"
232
+ other.mkdir()
233
+ value = json.loads(c.meta.read_text())
234
+ async with connect(
235
+ value["url"],
236
+ additional_headers={"Authorization": "Bearer " + value["token"]},
237
+ proxy=None,
238
+ ) as ws:
239
+ await ws.send(
240
+ json.dumps(
241
+ {
242
+ "id": "select",
243
+ "method": "thread/start",
244
+ "params": {"cwd": str(other)},
245
+ }
246
+ )
247
+ )
248
+ while json.loads(await ws.recv()).get("id") != "select":
249
+ pass
250
+ self.assertEqual(json.loads(c.meta.read_text())["cwd"], str(other))
251
+ self.assertEqual(json.loads(c.job.read_text())["cwd"], str(other))
252
+
253
+
254
+ class AttachmentRegressions(unittest.IsolatedAsyncioTestCase):
255
+ async def test_new_worker_reconnects_without_replaying_original_prompt(self):
256
+ with tempfile.TemporaryDirectory() as folder:
257
+ state = Path(folder)
258
+ root = service.folder(state)
259
+ job = root / "task.job.json"
260
+ meta = root / "task.connection.json"
261
+ atomic_json(job, {"binary": "/codex", "state": "running"})
262
+ old = {
263
+ "pid": os.getpid(),
264
+ "birth": process_birth(os.getpid()),
265
+ "url": "ws://127.0.0.1:1",
266
+ "token": "FAKE",
267
+ "home": folder,
268
+ "cwd": folder,
269
+ "thread": "saved",
270
+ }
271
+ atomic_json(meta, old)
272
+
273
+ class Process:
274
+ def __init__(self, done=False):
275
+ self.returncode = 0 if done else None
276
+ self.finished = asyncio.Event()
277
+
278
+ async def wait(self):
279
+ if self.returncode is None:
280
+ await self.finished.wait()
281
+ return self.returncode
282
+
283
+ def terminate(self):
284
+ self.returncode = 0
285
+ self.finished.set()
286
+
287
+ def kill(self):
288
+ self.terminate()
289
+
290
+ spawned = []
291
+
292
+ async def spawn(*argv, **kwargs):
293
+ spawned.append(argv)
294
+ if len(spawned) == 1:
295
+ atomic_json(
296
+ meta,
297
+ {
298
+ **old,
299
+ "pid": os.getppid(),
300
+ "birth": process_birth(os.getppid()),
301
+ "url": "ws://127.0.0.1:2",
302
+ "token": "NEW_FAKE",
303
+ },
304
+ )
305
+ return Process(done=len(spawned) > 1)
306
+
307
+ with patch("asyncio.create_subprocess_exec", spawn):
308
+ self.assertEqual(
309
+ await service.attach(state, "task", "/codex", ["original prompt"]),
310
+ 0,
311
+ )
312
+ self.assertEqual(len(spawned), 2)
313
+ self.assertIn("saved", spawned[1])
314
+ self.assertNotIn("original prompt", spawned[1])
315
+
316
+ def test_takeover_can_follow_session_but_not_native_separator(self):
317
+ self.assertTrue(parse_arguments(["attach", "task", "--takeover"]).takeover)
318
+ self.assertFalse(
319
+ parse_arguments(["attach", "task", "--", "--takeover"]).takeover
320
+ )
@@ -0,0 +1,217 @@
1
+ """Capacity recovery and audit regressions, without real accounts or requests."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import io
6
+ from pathlib import Path
7
+ import tempfile
8
+ import time
9
+ from types import SimpleNamespace
10
+ import unittest
11
+ from unittest.mock import patch
12
+
13
+ from pool import CAPACITY_MESSAGE, Pool, failure_kind
14
+ from router import Controller
15
+ from test_router import FakeAccount, FakeBackend, FakeClient, completed
16
+ from test_pool import Clock
17
+ from test_services import wait_for
18
+
19
+
20
+ class CapacityClassifierTests(unittest.TestCase):
21
+ def test_structured_and_exact_legacy_capacity_errors(self):
22
+ self.assertEqual(failure_kind("policyCapacityUnavailable"), "capacity")
23
+ for code in (
24
+ None,
25
+ "other",
26
+ "policyCapacityUnavailable",
27
+ "serverOverloaded",
28
+ "flexUnavailable",
29
+ ):
30
+ for message in (CAPACITY_MESSAGE, "■ " + CAPACITY_MESSAGE):
31
+ self.assertEqual(failure_kind(code, message), "capacity")
32
+
33
+ def test_policy_denials_are_not_reclassified_by_message(self):
34
+ for code in (
35
+ "cyberPolicy",
36
+ "misalignmentPolicyViolation",
37
+ "badRequest",
38
+ "sandboxError",
39
+ ):
40
+ self.assertIsNone(failure_kind(code, CAPACITY_MESSAGE))
41
+ self.assertIsNone(failure_kind("other", "This request mentions capacity"))
42
+
43
+
44
+ class CapacityRecoveryTests(unittest.IsolatedAsyncioTestCase):
45
+ async def capacity(self, turn):
46
+ message = completed(code="policyCapacityUnavailable", turn=turn)
47
+ message["params"]["turn"]["error"]["message"] = CAPACITY_MESSAGE
48
+ await self.c.event(message)
49
+
50
+ async def asyncSetUp(self):
51
+ self.tmp = tempfile.TemporaryDirectory()
52
+ self.clock = Clock()
53
+ self.pool = Pool(Path(self.tmp.name), clock=self.clock)
54
+ self.backend = FakeBackend()
55
+ self.c = Controller(
56
+ [FakeAccount("one"), FakeAccount("two")],
57
+ self.backend,
58
+ pool=self.pool,
59
+ clock=self.clock,
60
+ sleep=self.clock.sleep,
61
+ report=lambda _: None,
62
+ )
63
+ self.c.client = FakeClient()
64
+ self.c.last_turn["thread-1"] = {"threadId": "thread-1", "model": "chosen-model"}
65
+
66
+ async def asyncTearDown(self):
67
+ if self.c.recovery:
68
+ self.c.recovery.cancel()
69
+ await asyncio.gather(self.c.recovery, return_exceptions=True)
70
+ self.pool.close()
71
+ self.tmp.cleanup()
72
+
73
+ async def test_capacity_retries_every_five_minutes_without_cycling_accounts_or_models(
74
+ self,
75
+ ):
76
+ for i in range(4):
77
+ await self.capacity(str(i))
78
+ await self.c.recovery
79
+ self.assertEqual(self.clock.waits, [300, 300, 300, 300])
80
+ self.assertEqual(self.c.index, 0)
81
+ self.assertEqual(self.c.model_for("thread-1"), "chosen-model")
82
+ starts = [p for m, p in self.backend.calls if m == "turn/start"]
83
+ self.assertEqual(len(starts), 4)
84
+ self.assertTrue(all(p["threadId"] == "thread-1" for p in starts))
85
+ self.assertFalse(
86
+ any(
87
+ m in {"thread/resume", "account/login/start"}
88
+ for m, _ in self.backend.calls
89
+ )
90
+ )
91
+ with self.pool.db() as db:
92
+ reasons = [r[0] for r in db.execute("SELECT reason FROM blocks")]
93
+ self.assertFalse(reasons)
94
+
95
+ async def test_legacy_message_continues_saved_thread(self):
96
+ message = completed(code="other")
97
+ message["params"]["turn"]["error"]["message"] = CAPACITY_MESSAGE
98
+ await self.c.event(message)
99
+ await self.c.recovery
100
+ self.assertEqual(self.clock.waits, [300])
101
+ self.assertTrue(any(m == "turn/start" for m, _ in self.backend.calls))
102
+
103
+ async def test_final_policy_failure_cancels_an_earlier_capacity_retry(self):
104
+ await self.c.event(
105
+ {
106
+ "method": "error",
107
+ "params": {
108
+ "threadId": "thread-1",
109
+ "turnId": "turn-1",
110
+ "willRetry": False,
111
+ "error": {
112
+ "codexErrorInfo": "policyCapacityUnavailable",
113
+ "message": CAPACITY_MESSAGE,
114
+ },
115
+ },
116
+ }
117
+ )
118
+ await self.c.event(completed(code="cyberPolicy"))
119
+ await asyncio.gather(self.c.recovery, return_exceptions=True)
120
+ self.assertFalse(any(m == "turn/start" for m, _ in self.backend.calls))
121
+
122
+ async def test_native_error_and_final_turn_do_not_schedule_two_retries(self):
123
+ params = {
124
+ "threadId": "thread-1",
125
+ "turnId": "turn-1",
126
+ "error": {
127
+ "codexErrorInfo": "policyCapacityUnavailable",
128
+ "message": CAPACITY_MESSAGE,
129
+ },
130
+ }
131
+ await self.c.event({"method": "error", "params": {**params, "willRetry": True}})
132
+ self.assertIsNone(self.c.recovery)
133
+ await self.c.event(
134
+ {"method": "error", "params": {**params, "willRetry": False}}
135
+ )
136
+ await self.capacity("turn-1")
137
+ await self.c.recovery
138
+ self.assertEqual(self.clock.waits, [300])
139
+ self.assertEqual(sum(m == "turn/start" for m, _ in self.backend.calls), 1)
140
+
141
+ async def test_capacity_fallback_only_uses_explicitly_configured_model(self):
142
+ self.c.preferences["fallback_models"] = ["chosen-fallback"]
143
+ for i in range(4):
144
+ await self.capacity(str(i))
145
+ await self.c.recovery
146
+ self.assertEqual(self.c.model_for("thread-1"), "chosen-fallback")
147
+
148
+ async def test_restored_capacity_retry_retains_its_deadline(self):
149
+ await self.c.restore_recovery(
150
+ "thread-1",
151
+ {
152
+ "accepted": True,
153
+ "kind": "capacity",
154
+ "next_wake": 1500,
155
+ "account_label": "one",
156
+ "episode": {"account": 0, "start": 1000, "attempt": 1},
157
+ },
158
+ )
159
+ self.assertEqual(self.clock.waits, [500])
160
+ self.assertEqual(sum(m == "turn/start" for m, _ in self.backend.calls), 1)
161
+
162
+ async def test_missing_continuation_baseline_cannot_accept_old_completed_turn_as_new(
163
+ self,
164
+ ):
165
+ async def lost(method, params=None, **kwargs):
166
+ if method == "turn/start":
167
+ raise asyncio.TimeoutError()
168
+ if method == "thread/turns/list":
169
+ return {"data": [{"id": "old", "status": "completed"}]}
170
+ if method == "thread/read":
171
+ return {"thread": {"status": {"type": "idle"}}}
172
+ return {}
173
+
174
+ self.backend.call = lost
175
+ await self.c.continue_thread("thread-1", 0)
176
+ self.assertIn("thread-1", self.c.paused_uncertain)
177
+ self.assertFalse(self.c.pending_intents["thread-1"]["accepted"])
178
+
179
+ async def test_unrelated_wake_does_not_shorten_service_retry(self):
180
+ self.c.clock = time.time
181
+ self.c.sleep = asyncio.sleep
182
+ self.c.recovery = asyncio.create_task(self.c.recover("thread-1", 0, "capacity"))
183
+ await wait_for(lambda: self.c.episodes.get("thread-1", {}).get("attempt") == 1)
184
+ self.c.wake.set()
185
+ await asyncio.sleep(0.03)
186
+ self.assertFalse(self.c.recovery.done())
187
+ self.assertFalse(any(m == "turn/start" for m, _ in self.backend.calls))
188
+ # Explicit Retry is allowed to override the saved timer.
189
+ await self.c.control("retry", {})
190
+ await asyncio.wait_for(self.c.recovery, 1)
191
+ self.assertTrue(any(m == "turn/start" for m, _ in self.backend.calls))
192
+
193
+
194
+ class DoctorRegressionTests(unittest.IsolatedAsyncioTestCase):
195
+ async def test_incompatible_native_runtime_is_failure_exit(self):
196
+ import commands
197
+
198
+ with tempfile.TemporaryDirectory() as folder:
199
+ state = Path(folder)
200
+ Pool(state).close()
201
+
202
+ async def absent(*args, **kw):
203
+ raise OSError()
204
+
205
+ with (
206
+ patch("compatibility.check", side_effect=ValueError()),
207
+ patch("broker.exchange", absent),
208
+ contextlib.redirect_stdout(io.StringIO()),
209
+ ):
210
+ code = await commands.doctor(
211
+ SimpleNamespace(
212
+ restore_backup=False, self_test=False, backup=False
213
+ ),
214
+ state,
215
+ "fake",
216
+ )
217
+ self.assertEqual(code, 1)