Skip to content

refactor(runner): finish the lifecycle split with a typed context - #5754

Open
mmabrouk wants to merge 2 commits into
agent-config-editing-s2from
agent-config-editing-s7b
Open

refactor(runner): finish the lifecycle split with a typed context#5754
mmabrouk wants to merge 2 commits into
agent-config-editing-s2from
agent-config-editing-s7b

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member

Context

Part of the agent-config-editing stack. Targets agent-config-editing-s2. Read the stack bottom up.

Expect no behavior change. Every guard, every log string, every early return, and every ordering is preserved. The differences are mechanical: reads go through ctx.env, writes go through a named committer, and the two broad catches now rethrow invariant violations first. The characterization and keep-alive suites pin the behavior across the move.

s7a extracted the three separable units. The last three are not separable the same way. Mount, runtime, and harness session shared one closure holding six mutually recursive helpers. mountLocalAgentCwd calls activateAgentMountGuidance, which writes the daemon environment and the plan's system prompt. reSignAndRemountLocalCwd re-signs a credential and then calls mountLocalDurableCwd. Splitting them into modules meant either importing each other or sharing state through something.

Changes

AcquireContext is that something. Every helper takes ctx and captures nothing, so the six helpers become ordinary functions in three modules with no module importing another.

This is revision 2 of the context. An external security review rejected revision 1 because it stated its invariants in comments and exposed the mutable environment anyway. The reviewer's summary was exact: the type compiled but did not enforce what its comments promised. Revision 2 follows one rule, and the review's findings are recorded at the bottom of the file so the next reader sees what was tried and why it failed. If a comment says a unit may not do something, the type makes it impossible.

The three units:

  • mount-lifecycle.ts owns the durable cwd and the agent mount. They are not symmetric and the module says why: the cwd mount is keyed by session and participates in the safe-to-delete handshake, while the agent mount is keyed by artifact, lives at a sibling path, and carries the guidance that tells the model durable storage exists. One invariant is load-bearing. A local mount must complete before the sandbox provider takes the daemon environment maps by reference, or the guidance writes into maps nobody reads again.
  • runtime-lifecycle.ts owns the four handles nothing else owns: the loopback tool-MCP server, the OTLP bearer file, the Codex SQLite home, and the per-run agent directory. It is small, and the file says plainly why. The daemon environment is built once and is immutable afterwards, so there is no restart and no credential refresh to own yet. Inventing a restart() that throws would suggest a seam that does not exist.
  • harness-session-lifecycle.ts owns probe_capabilities and create_session plus session teardown. It takes an explicit input rather than the context, because nothing here shares mutable state or calls a sibling. Threading the context through anyway would imply a coupling that is not there.

Continuity stays best effort. A resume that throws is caught and falls through to a cold createSession. The worst outcome is a replay instead of a resume, which costs latency and never correctness. What a caller must not do is read a successful resumeSession as proof that history loaded, and loadedFromContinuity is what carries that distinction.

Tests / notes

  • 37 new tests: 324 lines covering the context's enforcement itself, plus unit coverage for each lifecycle unit.
  • Worth reading first: the rejected-revision notes at the bottom of acquire-context.ts. They are the argument for why the type looks the way it does, and a reviewer who skips them will want to simplify it back.

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 6, 2026 8:06pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e2214ab-5bbe-4054-a614-92dd02d49d36

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved sandbox session recovery by supporting native resume with a fallback to starting a fresh session.
    • Improved mount reliability with credential renewal and automatic remount attempts after connection interruptions.
    • Prevented invalid environment changes during runtime startup and teardown.
  • Reliability

    • Improved cleanup of temporary runtime files and active connections.
    • Added safer handling for session teardown and failed mount operations.
  • Tests

    • Expanded coverage for environment setup, session continuity, mounts, teardown, and recovery behavior.

Walkthrough

The PR decomposes sandbox environment acquisition into controlled context, runtime, mount, and harness lifecycle modules. It centralizes state transitions, freezes daemon environment before provider startup, and adds seam tests for ownership and ordering.

Changes

Environment lifecycle decomposition

Layer / File(s) Summary
Acquire context contract
services/runner/src/environment/acquire-context.ts, services/runner/src/environment/acquire-context-impl.ts
Adds AcquireContext, read-only environment views, explicit mutation methods, invariant errors, daemon freezing, mount state, runtime handles, guidance, and remount budgets.
Runtime environment bootstrap
services/runner/src/environment/runtime-lifecycle.ts, services/runner/src/engines/sandbox_agent/environment-setup.ts, services/runner/src/engines/sandbox_agent/environment.ts
Moves daemon and Pi environment construction, OTLP file creation, Codex paths, and environment merging into buildRuntimeEnvironment. The composer consumes its result and freezes daemon environment before provider startup.
Mount and runtime lifecycle
services/runner/src/environment/mount-lifecycle.ts, services/runner/src/environment/runtime-lifecycle.ts, services/runner/src/engines/sandbox_agent/environment.ts
Extracts mount, remount, guidance, ENOTCONN recovery, runtime quiescing, teardown, unmount safety, and runtime-file cleanup operations.
Harness session lifecycle
services/runner/src/environment/harness-session-lifecycle.ts, services/runner/src/engines/sandbox_agent/environment.ts
Extracts capability probing, continuity resume, cold-session fallback, timing, and graceful session teardown.
Composer integration and seam tests
services/runner/tests/unit/acquire-context.test.ts, services/runner/tests/unit/environment-units.test.ts
Tests context ownership, invariant handling, mount transitions, remount budgets, lifecycle extraction, ordering, delegation, and environment-setup purity.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant environment.ts
  participant mount-lifecycle
  participant runtime-lifecycle
  participant sandbox provider
  environment.ts->>mount-lifecycle: Mount and remount local resources
  environment.ts->>environment.ts: Freeze daemon environment
  environment.ts->>sandbox provider: Start with frozen environment
  environment.ts->>runtime-lifecycle: Quiesce runtime and remove files
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the lifecycle split and typed context introduced by the pull request.
Description check ✅ Passed The description directly explains the typed context, lifecycle modules, behavior preservation, and added tests.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent-config-editing-s7b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

* reviewer's summary was exact: "The type compiles, but it does not enforce what its comments
* promise." So this revision follows one rule:
*
* IF A COMMENT SAYS A UNIT MAY NOT DO SOMETHING, THE TYPE MUST MAKE IT IMPOSSIBLE.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This rule is the reason the type looks the way it does. Read it before you read the type.

This is revision 2. An external security review rejected revision 1. It stated its invariants in comments and exposed the mutable environment anyway, and the reviewer's summary was exact. The type compiled, and it did not enforce what its comments promised.

So every restriction here is expressed in the type. A unit that may not write a field cannot reach it. The findings from that review are recorded at the bottom of this file, because each one describes a way this type could have looked correct and enforced nothing.

If this type looks heavier than it needs to be, that history is the answer. Simplifying it back to comments recreates the version that was rejected.

* THE THREE ORDERING INVARIANTS
* ============================================================================================
*
* ---- INVARIANT 1: LOCAL MOUNTS RUN BEFORE THE PROVIDER FREEZES THE DAEMON ENV ----

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This invariant is load bearing and it is easy to break by accident.

The sandbox provider takes the daemon environment maps by reference. After that point the maps are frozen in effect, because nothing reads them again. A local mount must therefore complete before that moment.

The failure is silent. The guidance path writes AGENT_MOUNT_ENV_VAR to tell the model that durable storage exists. A local mount that lands after the freeze writes into a map nobody reads, so the harness never learns about its own durable storage, and nothing throws.

AcquireInvariantError exists for this class. Every broad catch in these units calls rethrowIfInvariant first, so an invariant violation cannot be swallowed by a catch written for provider errors.

* CONTINUITY IS BEST EFFORT, AND THAT IS DELIBERATE. A resume that throws is caught, logged, and
* falls through to a cold `createSession`. The worst outcome is a conversation that replays
* instead of resuming, which costs latency and never correctness. What the caller must NOT do is
* treat a successful `resumeSession` as proof that history loaded — see `loadedFromContinuity`.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This unit takes an explicit input rather than the acquire context, and the difference is deliberate.

The mount unit needs the context because its six helpers share mutable state and call each other. Nothing here does. Probing and opening are straight line, their inputs are read only, and their outputs are returned rather than assigned.

Threading the context through anyway would imply a coupling that does not exist, and the next reader would look for it.

One caution that predates this PR and stays true. A successful resumeSession does not prove that history loaded. loadedFromContinuity carries that distinction, and a caller that treats the two as the same will replay a conversation it believes was resumed.

* helpers below were mutually recursive closures sharing one scope in `acquireEnvironment`; they
* are now ordinary functions that take `ctx` and capture nothing.
*
* ZERO BEHAVIOR CHANGE. Every guard, every log string, every early return and every ordering is

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This whole file is a code move. Every guard, every log string, every early return, and every ordering is preserved.

The differences are mechanical and worth knowing while reading. Reads go through ctx.env. Writes go through a named committer. The two broad catches now begin with rethrowIfInvariant.

The two mounts stay asymmetric because they really are. The durable cwd is keyed by session, signed with signMount, and participates in the safe to delete handshake at teardown. The agent mount is keyed by artifact, lives at a sibling path, and carries the guidance. Only one of them runs each path, which is why these are separate functions instead of one function with a mount kind parameter.

@mmabrouk

mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mmabrouk

mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
services/runner/src/environment/acquire-context.ts (1)

158-163: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused AcquireContext writers or give them callers.

environment.ts still writes environment.closeToolMcp directly, and setOtlpAuthFilePath / setCodexSqliteHome have no TypeScript callers. Eliminate these methods if the runtime interface should only expose the complete writer list.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e34935c-e571-4d82-871e-61cec1649c39

📥 Commits

Reviewing files that changed from the base of the PR and between 21957e1 and 9d2ff4c.

📒 Files selected for processing (9)
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/environment/acquire-context-impl.ts
  • services/runner/src/environment/acquire-context.ts
  • services/runner/src/environment/harness-session-lifecycle.ts
  • services/runner/src/environment/mount-lifecycle.ts
  • services/runner/src/environment/runtime-lifecycle.ts
  • services/runner/tests/unit/acquire-context.test.ts
  • services/runner/tests/unit/environment-units.test.ts

Comment on lines 321 to 323
environment.destroy = async (opts?: { reason?: TeardownReason }) => {
if (environment.destroyed) return;
environment.destroyed = true;

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Create ctx before you assign environment.destroy.

environment.destroy is assigned at line 321 and calls ctx.recordCwdUnmountResult at line 356. ctx is a const declared at line 403, so it stays in the temporal dead zone until that line runs. Two consequences follow.

  1. If createAcquireContext at line 403 throws, no catch covers lines 397-433. acquireEnvironment then rejects without calling environment.destroy, and plan.workspace.skillsCleanup() never runs.
  2. Any later edit that calls environment.destroy() before line 403 raises ReferenceError: Cannot access 'ctx' before initialization instead of tearing down.

Move the createAcquireContext call and mountDeps above the environment.destroy assignment. The order of executed side effects does not change.

Also applies to: 398-403

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, fix queued. Confirmed still current, through the tip of the stack: environment.destroy is assigned at the top of acquireEnvironment and closes over ctx, but const { context: ctx } = createAcquireContext(...) runs after that assignment and before the surrounding try block starts. If createAcquireContext throws, nothing calls environment.destroy, so plan.workspace.skillsCleanup() is skipped.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@mmabrouk have exceeded the limit for the number of chat messages per hour. Please wait 29 minutes and 25 seconds before sending another message.

Comment on lines +159 to +166
export async function teardown(input: {
sandbox: { destroySession?: (id: string) => Promise<unknown> } | undefined;
session: { id: string } | undefined;
/** True when the pause path already sent its own cancel. */
alreadyRequested: boolean;
}): Promise<void> {
if (!input.session || input.alreadyRequested) return;
await input.sandbox?.destroySession?.(input.session.id).catch(() => {});

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect implementations and teardown callers without executing repository code.
rg -n -C 6 '\bdestroySession\s*[:(]' services/runner/src
rg -n -C 6 '\bteardown\s*\(' services/runner/src

Repository: Agenta-AI/agenta

Length of output: 12458


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
async function chainedAfterSyncThrow() {
  const sandbox = { destroySession: () => { throw new Error('sync destroy'); } };
  await sandbox?.destroySession?.('id').catch(() => {});
}

async function caughtSyncThrowViaTry() {
  const sandbox = { destroySession: () => { throw new Error('sync destroy'); } };
  try {
    await sandbox?.destroySession?.('id');
  } catch {}
}

Promise.allSettled([chainedAfterSyncThrow(), caughtSyncThrowViaTry()]).then((results) => {
  console.log(JSON.stringify(results, null, 2));
}).catch((err) => {
  console.log('callback error:', String(err));
});
JS

echo '--- relevant harness teardown ---'
sed -n '145,175p' services/runner/src/environment/harness-session-lifecycle.ts

echo '--- destroySession definitions ---'
rg -n -C 4 'destroySession\s*:|destroySession\s*\(|export interface .*destroy|interface .*destroy' services/runner/src

Repository: Agenta-AI/agenta

Length of output: 2599


Catch synchronous destroySession errors.

A synchronous exception from destroySession happens before the returned promise is available for .catch, so teardown rejects. Use try/catch around the await to keep the “only refuses to throw” contract.

Proposed fix
-  await input.sandbox?.destroySession?.(input.session.id).catch(() => {});
+  try {
+    await input.sandbox?.destroySession?.(input.session.id);
+  } catch {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function teardown(input: {
sandbox: { destroySession?: (id: string) => Promise<unknown> } | undefined;
session: { id: string } | undefined;
/** True when the pause path already sent its own cancel. */
alreadyRequested: boolean;
}): Promise<void> {
if (!input.session || input.alreadyRequested) return;
await input.sandbox?.destroySession?.(input.session.id).catch(() => {});
export async function teardown(input: {
sandbox: { destroySession?: (id: string) => Promise<unknown> } | undefined;
session: { id: string } | undefined;
/** True when the pause path already sent its own cancel. */
alreadyRequested: boolean;
}): Promise<void> {
if (!input.session || input.alreadyRequested) return;
try {
await input.sandbox?.destroySession?.(input.session.id);
} catch {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, fix queued. Confirmed still current, through the tip of the stack: teardown() still uses await input.sandbox?.destroySession?.(input.session.id).catch(() => {}), which only catches a promise rejection, not a synchronous throw from destroySession itself. The function's own docstring says it must never throw.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@mmabrouk have exceeded the limit for the number of chat messages per hour. Please wait 30 minutes and 45 seconds before sending another message.

Comment on lines +107 to +113
export function removeRuntimeFiles(input: RuntimeFilesInput): void {
if (input.runAgentDir)
rmSync(input.runAgentDir, { recursive: true, force: true });
if (input.otlpAuthFilePath) rmSync(input.otlpAuthFilePath, { force: true });
if (input.codexSqliteHome)
rmSync(input.codexSqliteHome, { recursive: true, force: true });
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard the rmSync calls so teardown cannot throw.

environment.destroy states it must never throw, and it calls removeRuntimeFiles at environment.ts line 389, before plan.workspace.skillsCleanup() at line 395. force: true suppresses ENOENT only. A rmSync on runAgentDir can still throw EACCES, EBUSY, or ENOTCONN, for example when the per-run dir sits under a stale FUSE node. The rejection then skips skillsCleanup(), leaks the skills temp root, and rejects destroy().

The sibling delete at environment.ts lines 368-377 already wraps rmSync in try/catch, so this unit is the outlier.

🛡️ Proposed fix
-export function removeRuntimeFiles(input: RuntimeFilesInput): void {
-  if (input.runAgentDir)
-    rmSync(input.runAgentDir, { recursive: true, force: true });
-  if (input.otlpAuthFilePath) rmSync(input.otlpAuthFilePath, { force: true });
-  if (input.codexSqliteHome)
-    rmSync(input.codexSqliteHome, { recursive: true, force: true });
-}
+export function removeRuntimeFiles(
+  input: RuntimeFilesInput,
+  log: Log = () => {},
+): void {
+  const remove = (path: string | undefined, recursive: boolean) => {
+    if (!path) return;
+    try {
+      rmSync(path, { recursive, force: true });
+    } catch (err) {
+      // Teardown must always reach the end; a strand here is preferable to a rejected destroy.
+      log(`runtime file cleanup failed path=${path}: ${(err as Error).message}`);
+    }
+  };
+  remove(input.runAgentDir, true);
+  remove(input.otlpAuthFilePath, false);
+  remove(input.codexSqliteHome, true);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function removeRuntimeFiles(input: RuntimeFilesInput): void {
if (input.runAgentDir)
rmSync(input.runAgentDir, { recursive: true, force: true });
if (input.otlpAuthFilePath) rmSync(input.otlpAuthFilePath, { force: true });
if (input.codexSqliteHome)
rmSync(input.codexSqliteHome, { recursive: true, force: true });
}
export function removeRuntimeFiles(
input: RuntimeFilesInput,
log: Log = () => {},
): void {
const remove = (path: string | undefined, recursive: boolean) => {
if (!path) return;
try {
rmSync(path, { recursive, force: true });
} catch (err) {
// Teardown must always reach the end; a strand here is preferable to a rejected destroy.
log(`runtime file cleanup failed path=${path}: ${(err as Error).message}`);
}
};
remove(input.runAgentDir, true);
remove(input.otlpAuthFilePath, false);
remove(input.codexSqliteHome, true);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, fix queued. Confirmed still current, through the tip of the stack: removeRuntimeFiles runs three bare rmSync calls with no try/catch, while the sibling delete in environment.ts (agent mountpoint cleanup) already wraps its rmSync and logs on failure. force: true only suppresses ENOENT, not EACCES/EBUSY/ENOTCONN.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@mmabrouk have exceeded the limit for the number of chat messages per hour. Please wait 29 minutes and 21 seconds before sending another message.

Comment on lines +128 to +134
export interface BuildRuntimeEnvironmentInput {
plan: never;
request: never;
piSkillSnapshot: unknown;
log: Log;
deps: { buildDaemonEnv?: typeof buildDaemonEnv };
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

BuildRuntimeEnvironmentInput declares plan and request as never, so the compiler checks nothing across this new module boundary. The root cause is the input type; the as never casts at the call site are its consequence. tsc --strict cannot catch a field rename in RunPlan or AgentRunRequest at either site.

  • services/runner/src/environment/runtime-lifecycle.ts#L128-L134: type plan as RunPlan and request as AgentRunRequest, then delete the as unknown as structural aliases p and r in buildRuntimeEnvironment.
  • services/runner/src/engines/sandbox_agent/environment-setup.ts#L180-L186: remove the plan as never and request as never casts and pass plan and request directly.

As per coding guidelines: "Maintain strict TypeScript correctness with tsc --strict".

📍 Affects 2 files
  • services/runner/src/environment/runtime-lifecycle.ts#L128-L134 (this comment)
  • services/runner/src/engines/sandbox_agent/environment-setup.ts#L180-L186

Source: Coding guidelines

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, fix queued. Confirmed still current, through the tip of the stack: BuildRuntimeEnvironmentInput still declares plan: never and request: never, so the as never / as unknown as casts at the call sites remain the only type check across this boundary.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@mmabrouk have exceeded the limit for the number of chat messages per hour. Please wait 29 minutes and 27 seconds before sending another message.

Comment on lines +296 to +304
it("every operational catch rethrows an invariant violation first", () => {
// Without this the freeze throw dies in mountLocalAgentCwd's catch and the run continues
// with a harness that cannot see its durable storage.
const source = SRC("environment/mount-lifecycle.ts");
assert.ok(source.includes("catch (err)"), "the unit still has an operational catch");
assert.ok(
source.includes("rethrowIfInvariant(err)"),
"an operational catch must start with rethrowIfInvariant",
);

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate each operational catch block.

The current assertion only proves that one rethrowIfInvariant(err) exists. A later catch (err) can swallow an AcquireInvariantError and this test still passes. Iterate over every catch block and assert that rethrowIfInvariant(err) is its first statement.

Proposed test change
-    const source = SRC("environment/mount-lifecycle.ts");
-    assert.ok(source.includes("catch (err)"), "the unit still has an operational catch");
-    assert.ok(
-      source.includes("rethrowIfInvariant(err)"),
-      "an operational catch must start with rethrowIfInvariant",
-    );
+    const source = CODE_ONLY(SRC("environment/mount-lifecycle.ts"));
+    const catches = [...source.matchAll(/catch\s*\(\s*err\s*\)\s*\{/g)];
+    assert.ok(catches.length > 0, "the unit still has an operational catch");
+    for (const match of catches) {
+      assert.match(
+        source.slice(match.index),
+        /^catch\s*\(\s*err\s*\)\s*\{\s*rethrowIfInvariant\(err\);/,
+        "an operational catch must start with rethrowIfInvariant",
+      );
+    }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, fix queued. Confirmed still current, through the tip of the stack: the test still asserts only that mount-lifecycle.ts contains the strings catch (err) and rethrowIfInvariant(err) somewhere, not that every catch block starts with it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@mmabrouk have exceeded the limit for the number of chat messages per hour. Please wait 29 minutes and 4 seconds before sending another message.

@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 21957e1 to 706b0ad Compare August 6, 2026 11:12
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch 2 times, most recently from 6b52aeb to 6e7026c Compare August 6, 2026 11:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 706b0ad to 782b50f Compare August 6, 2026 11:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 6e7026c to 0e1f7d0 Compare August 6, 2026 11:14
@mmabrouk
mmabrouk marked this pull request as ready for review August 6, 2026 12:30
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. refactoring A code change that neither fixes a bug nor adds a feature labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-5754.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-5754-f210696
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-08-06T22:17:35.574Z

@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from e1ab55e to 3379ea2 Compare August 6, 2026 14:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 1ffea8c to 7e44489 Compare August 6, 2026 14:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 3379ea2 to d650d49 Compare August 6, 2026 14:23
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 7e44489 to d4ea608 Compare August 6, 2026 14:24
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from d650d49 to 5b5d744 Compare August 6, 2026 14:45
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from d4ea608 to 8647567 Compare August 6, 2026 14:45
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 5b5d744 to 0339026 Compare August 6, 2026 14:59
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 8647567 to 41ff499 Compare August 6, 2026 14:59
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 0339026 to 5a26619 Compare August 6, 2026 15:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 41ff499 to c773584 Compare August 6, 2026 15:15
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from c773584 to 31fd7c1 Compare August 6, 2026 15:22
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 956bf3e to 454342f Compare August 6, 2026 15:44
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 31fd7c1 to 5ef8fae Compare August 6, 2026 15:45
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 454342f to 3e8840b Compare August 6, 2026 16:01
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 5ef8fae to 8f0bc8b Compare August 6, 2026 16:01
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from 8f0bc8b to cabdc99 Compare August 6, 2026 16:13
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s2 branch from 3e8840b to 7e460f7 Compare August 6, 2026 17:14
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s7b branch from cabdc99 to 3f4fc7e Compare August 6, 2026 17:15
…ss-session units (S7b)

The six mutually recursive mount closures become top-level functions on
the externally reviewed AcquireContext: read-only environment view,
committer-only writes, an invariant error that escapes operational
catches, and the daemon-env freeze enforced before the provider by a
source-position test. environment-setup returns to a pure planner;
daemon-env construction moves to the runtime unit with the assign-order
rule documented. Zero behavior change; 52 seam tests; the stage-name
guard proves all seven timing stages still fire.
…ordering, sync teardown throw, guarded removals, real boundary types, first-statement invariant assertion)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant