Skip to content

[codex] Fix benchmark harness and runtime trace regressions#212

Open
briancavalier wants to merge 5 commits into
mainfrom
codex/benchmark-harness-regressions
Open

[codex] Fix benchmark harness and runtime trace regressions#212
briancavalier wants to merge 5 commits into
mainfrom
codex/benchmark-harness-regressions

Conversation

@briancavalier

Copy link
Copy Markdown
Owner

Summary

  • fix the benchmark:runtime-loops harness after scope/token and task lifecycle API changes
  • remove avoidable runtime-boundary provideAll({}) overhead from run, runTask, and runFork
  • save the regression investigation and benchmark evidence under benchmarks/results/2026-05-26-benchmark-harness-and-trace-regressions.md

Root Cause

The broken runtime-loops benchmark was using stale APIs:

  • scope(name) now creates a scope token and must be paired with withScope(...)
  • task cancellation/disposal in the benchmark needed task.interrupt() instead of the old disposal helper path

The trace regressions were not mainly in trace-policy logic. run and runTask always wrapped execution in provideAll({}), which adds a general handler frame around every runtime execution. After fefff46 added cleanup-drain semantics for interrupted finalizers, that default environment wrapper became visible in hot paths. This change keeps the cleanup semantics and handles the default empty environment directly at the runtime boundary.

Impact

  • benchmark:runtime-loops runs again and preserves existing case names for historical comparisons.
  • pure runtime baseline returns from about 1178 ns/op to about 235 ns/op in the investigated worktree.
  • successful assertPromise labels/off recover most of the regression signal from the weekly benchmark run.
  • handled fail labels/off remains slower than the older May 7 artifact; the investigation note isolates that to synchronous control(Fail, ...) cleanup-drain overhead and leaves it as follow-up work.

Validation

  • corepack pnpm benchmark:trace
  • corepack pnpm benchmark:runtime-context
  • corepack pnpm benchmark:runtime-loops
  • corepack pnpm build
  • corepack pnpm lint
  • corepack pnpm test
  • corepack pnpm typecheck

@briancavalier briancavalier marked this pull request as ready for review May 26, 2026 16:37

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb14bbe74e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Fx.ts Outdated
*/
export const runTask = <const R>(f: Fx<Async | HandlerCapture<string> | Interrupt, R>, options: RunForkOptions = {}): Task<R, never> => {
return runFork(f.pipe(provideAll({})), {
export const runTask = <const R>(f: Fx<Async | HandlerCapture<string> | Interrupt | Get<Record<PropertyKey, unknown>>, R>, options: RunForkOptions = {}): Task<R, never> => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep env effects out of runtime entrypoint signatures

Widening runTask/runPromise/run to accept Get<Record<PropertyKey, unknown>> removes a useful type-safety boundary: programs that require context (for example fx(function* ({ userId }: { userId: string }) { ... })) now type-check at the runtime boundary even when no provider is installed, and they will be resumed with {}. That turns what used to be a compile-time error into runtime undefined values (or crashes) in ordinary user code, which is a behavioral regression for environment-dependent Fx programs.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a06ec82848

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/internal/runFork.ts Outdated
Comment on lines +203 to +204
} else if (isEffect(ir.value) && ir.value._fxEffectId === EnvEffectId) {
ir = resumeWithRuntimeContext(i, runtimeContext, {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reuse a single default env object per task run

This resumes each unhandled Get with a fresh {} on every request, which changes runtime behavior from the previous provideAll({}) boundary: multiple get() calls in one execution no longer observe the same context object. Programs that cache on context identity or intentionally mutate optional context between get() calls will now break (or diverge) even though they were valid before this refactor. Creating one per-run empty env object and reusing it for all Get resumptions preserves prior semantics and avoids extra allocations.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50cc82516c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Fx.ts Outdated
type MissingRuntimeEnvArgs<E> =
[RuntimeEnv<E>] extends [never]
? []
: {} extends RuntimeEnv<E>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require env when any Get variant has required keys

MissingRuntimeEnvArgs treats environment requirements as optional when RuntimeEnv<E> is a union containing any optional branch, because {} extends (A | B) succeeds if it matches either side. That means composed programs like requiredFx.pipe(andThen(optionalFx)) (or any union of Get<{required: ...}> with Get<{optional?: ...}>) can now pass run/runTask/runPromise without a provider and receive {} at runtime, reintroducing the runtime-undefined failure mode this type gate is meant to prevent.

Useful? React with 👍 / 👎.

Comment thread src/internal/runFork.ts Outdated
runtimeContext?: RuntimeContext
): Promise<A> => {
let interrupting: Promise<void> | undefined
const defaultEnv = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve one default env object across fork tree

Allocating defaultEnv inside each runForkLoop gives every forked task its own {} instead of sharing the parent run’s default environment object. Before this refactor, runTask wrapped the whole run with provideAll({}), so parent and children observed the same default env; now a child get() mutation is invisible to the parent (and siblings), which is a behavioral change for programs relying on env identity/coordination across forks.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ca204e362

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/internal/runFork.ts Outdated
Comment on lines +30 to +31
const defaultEnv = parentRuntimeContext?.defaultEnv ?? {}
const runtimeContext = withDefaultEnv(parentRuntimeContext, defaultEnv)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Isolate default env for each runtime entrypoint

Compute defaultEnv from a fresh object at the runtime boundary instead of inheriting currentRuntimeContext().defaultEnv; as written, calling runTask/runPromise from inside an active Fx execution (for example in an assertPromise callback) causes the nested run to reuse the parent run’s mutable env object. That lets separate entrypoints read and overwrite each other’s get() state, which regresses the previous per-entrypoint isolation and can produce action-at-a-distance bugs.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3eccfb8fed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Fx.ts
type RequiredRuntimeEnv<E> =
Extract<E, Get<Record<PropertyKey, unknown>>> extends infer G
? G extends Get<infer A>
? {} extends A ? never : A

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Distribute required-env check over union context objects

RequiredRuntimeEnv currently uses {} extends A on the whole inferred context type, so a single Get with a union like Get<{name: string} | {name?: string}> is treated as optional and run/runTask/runPromise accept it without provideAll. That reintroduces the runtime-boundary unsoundness this guard is meant to prevent: the program can be resumed with {} and observe undefined for required fields. Fresh evidence: a minimal repro with fx(function* ({name}: {name: string} | {name?: string}) { ... }) compiles through runTask(...) unless manually blocked.

Useful? React with 👍 / 👎.

Sign up for free to 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