Skip to content

Run.getReadable() fetches the whole run (resolveData 'all') just to resolve the encryption key #4645

Closed

Description

@rpinckne

Run#getEncryptionKey() calls world.runs.get(this.runId) with the default resolveData: 'all', but getEncryptionKeyForRun only reads runId and deploymentId. Every other runs.get in run.ts already passes resolveData: 'none'. On world-vercel the default maps to remoteRefBehavior=resolve, so every getRun(id).getReadable() on a new Run has the server resolve and return the run's input and output before the stream can be read. For runs started with a large input, that cost repeats for every new Run handle that reads a stream.

getForwardedWritableEncryptionKey in serialization.ts makes the same call when it has no deploymentId.

Versions: @workflow/core 5.0.1 and 5.0.0-beta.58, and main at 2d2159e. Same output on all three.

// npm i @workflow/core@5.0.1 @workflow/world@5.0.1 @workflow/world-local@5.0.1 && node repro.mjs
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createWorld } from '@workflow/world-local';
import { SPEC_VERSION_CURRENT } from '@workflow/world';
import { getRun, setWorld } from '@workflow/core/runtime';
import { dehydrateStepArguments } from '@workflow/core/serialization';

const world = createWorld({ dataDir: mkdtempSync(join(tmpdir(), 'wf-')), recoverActiveRuns: false });
setWorld(world);
const input = await dehydrateStepArguments(['x'.repeat(5_000_000)], 'run', undefined);
const { run } = await world.events.create(null, {
  eventType: 'run_created', specVersion: SPEC_VERSION_CURRENT,
  eventData: { deploymentId: 'dpl_1', workflowName: 'wf', input },
});

const get = world.runs.get.bind(world.runs);
world.runs.get = async (id, opts) => {
  const r = await get(id, opts);
  console.log(`runs.get(${JSON.stringify(opts ?? {})}) -> input ${r.input?.byteLength ?? 0} bytes`);
  return r;
};

const reader = getRun(run.runId).getReadable().getReader();
reader.read().catch(() => {});
await new Promise((r) => setTimeout(r, 300));
await reader.cancel();
process.exit(0);

Expected: runs.get({"resolveData":"none"}) -> input 0 bytes
Actual: runs.get({}) -> input 5000012 bytes

Fix: pass { resolveData: 'none' } at both call sites.

Activity

  1. added a commit that references this issue on Oct 20, 2026
    0bc17d7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions