Skip to content

Eight pre-existing findings from a review sweep (none branch-caused) #807

Description

@sroussey

An automated review sweep over #790 surfaced these. None are caused by that branch — it touches 26 files and none of the ones below. I verified each against origin/main before filing; where the review's framing was wrong, I've said so.

Filed together because they came from one pass, but they are unrelated to each other and want separate fixes.


Correctness defects

1. deleteRunEntries orphans the rows it deliberately leaves behind

packages/task-graph/src/storage/FsFolderTaskOutputRepository.ts:249verified

It deletes only the listed rows, then calls deleteBlobsByPrefix(nsPrefix), which sweeps every sidecar blob under the run prefix:

await deleteRowsBounded(doomed, (key) => this.storage.delete(key));
this.emit("output_pruned");
await this.deleteBlobsByPrefix(nsPrefix);

A run that crashes after writing private cache rows and then resumes under the same runId hits this: RunPrivateCacheRepo.writeSet covers only the current process's writes, so clearRun names just the new rows. The earlier process's rows survive by design — but their blobs are unlinked anyway. Those rows now carry CacheRefs resolving to nothing, and CacheCoordinator.lookup returns them as a hit when no consumer hint is set, so the dangling ref reaches the caller rather than degrading to a miss.

The blob sweep needs to be scoped to the rows actually deleted.

2. ConditionalTask abort leaves the previous run's routing readable

packages/task-graph/src/task/ConditionalTask.ts:211verified

if (context.signal?.aborted) {
  return undefined;
}

// Clear previous branch activation state
this.activeBranches.clear();
this.portActiveStatus = undefined;

The early return precedes the clear, so after an aborted re-run getPortActiveStatus() keeps reporting which branch the previous input selected instead of falling back to the config.branches derivation. The clear belongs above the abort check, beside activeBranches.clear().

3. ConditionalTask.outputSchema decides the shape from branches[0] alone

packages/task-graph/src/task/ConditionalTask.ts:478verified

const hasFunctionBranches = branches.length > 0 && typeof branches[0].condition === "function";

A heterogeneous array — a serialized branch first, a function branch second, or the reverse — publishes the wrong port set. Dataflows wired off the ports the run actually writes then fail the graph's compatibility check, and RunScheduler reads a getPortActiveStatus() map whose keys don't match the declared ports.

4. xAI ignores its own effort policy

providers/xai/src/ai/common/Xai_Client.ts:124verified

getXaiReasoningEffort returns EFFORT_TO_XAI[model.effort] whenever model.effort is set, never consulting xaiEffortPolicy. That policy declares {supported: []} for grok-4-fast-non-reasoning and the xAI image models, so a ModelConfig carrying an inherited or defaulted effort still gets reasoning_effort attached — a parameter the provider has just declared the model does not accept. Reached from buildChatParams, the structured-generation path, and the rewriter/summary run-fns, none of which consult the policy either.


Resource retention

5. IteratorTask retains up to 64 completed subgraph clones

packages/task-graph/src/task/IteratorTask.ts:450verified (cap and clear points; retention behavior read, not measured)

concurrencyLimit is optional and undefined by default, so iterationGraphCap() falls back to a hard cap of 64. clearIterationGraphs() is called only at the start of the next map/reduce run, so a MapTask over a large collection keeps the 64 most recently completed TaskGraph clones — each holding its tasks and their runOutputData — alive after the map finishes. The retention is on the domain object and happens whether or not a UI is attached; it exists as a UI affordance.


Release tooling — decisions, not obviously bugs

Flagging rather than asserting: both may have been intentional. Both are on main today.

6. publish-all has no test gate

package.json:54

"publish-all": "bun run format && bun run rebuild && bun run bunset && bun run publish-login && bun run publish-workspaces"

No bun run test in the chain, so nothing the vitest suite would catch blocks a release. bunset only versions and tags.

7. overrides is down to 3 entries

package.jsonverified: main currently has 3

The review described this as a regression "from 16 to 3", which is wrong — main has had it this way across the 0.3.42–0.3.44 releases. Whether the dropped pins should come back is a real question (the @codemirror/* / @lezer/* single-instance pins in particular guarded against duplicate editor instances in the web example), but restoring them moves the lockfile and should be its own change.


Refactor opportunity

8. The effort-policy module is duplicated across six providers

providers/{anthropic,deepseek,google-gemini,openai,openrouter,xai}/src/ai/common/*_EffortPolicy.tsverified

151 lines across six files, each with the same ALL/NONE ModelEffortPolicy literals, the same modelName(model) helper reading provider_config.model_name, and the same stampEffortOptions(record, policy(...)) call in its *_ModelSearch.ts. Only the id-matching differs.

MODEL_EFFORTS and stampEffortOptions already live in @workglow/ai/worker; a makeEffortPolicy(matchers) helper there would collapse the boilerplate. As it stands, any change to the policy shape — adding a per-model default, say — has to be made six times or drift. Item 4 above is arguably the first instance of that drift.

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