Skip to content

pathInTargetOutputLayout misattributes files across equal-length target names (false AB6017) #123

Description

@ScriptedAlchemy

Summary

Selecting multiple targets whose names have the same length (claude, cursor, plugin are all 6 chars) makes artifact MCP validation attribute each target's files to the other targets, producing spurious AB6017 reference-count diagnostics. A real consumer (cargo-conductor) had to drop from targets: ['claude','codex','cursor','plugin'] to ['plugin','portable'] to get a green build.

Root cause

packages/agent-bundle/src/build/artifact-layout.ts:

export const pathInTargetOutputLayout = (
  targetPath: string,
  target: string,
  layout: TargetArtifactOutputLayout | undefined,
): boolean => isDirectOutputLayoutPath(targetPath.slice(target.length + 1), layout);

The target prefix is stripped by length only — the function never checks that targetPath actually starts with ${target}/. So checking cursor/mcp/mcp-x.mjs against target claude slices 7 chars and yields mcp/mcp-x.mjs, which is inside the MCP layout ⇒ match.

validate-artifact-mcp.ts then filters the whole artifact file list per target:

const mcpEntries = options.files.filter((file) => pathInTargetOutputLayout(file.path, target.name, mcpLayout));

so with equal-length sibling targets, every target's mcp/ bundles are counted as entries of every other same-length target, and the per-target reference accounting fails (AB6017: unreferenced/multiply-referenced MCP entries).

The hooks validator is only accidentally immune: validate-artifact-hooks.ts checks hook.path.startsWith(expectedPrefix) separately before calling pathInTargetOutputLayout.

Repro

agent-bundle.config.ts with any MCP server and targets: ['claude', 'cursor'] (or any two same-length target names) → agent-bundle build → AB6017.

Suggested fix

Guard the prefix before slicing (the counterpart targetArtifactPath already produces ${target}/${path}):

export const pathInTargetOutputLayout = (targetPath, target, layout) =>
  targetPath.startsWith(`${target}/`) &&
  isDirectOutputLayoutPath(targetPath.slice(target.length + 1), layout);

Observed on the 560124af preview; the slice is unchanged at current main (3f8f08fc).

Activity

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions