fix(crabbox): staging recovery fails when any local claim is unattached (#159848)

Crabbox records provider resources that are not yet attached to a
repository with an empty repoRoot (ClaimLeaseTargetForConfig with
allowEmptyRepoRoot), and refuses to replace an attached root without
--reclaim. The staging claims inventory required every repoRoot to be
absolute, so one unattached claim anywhere in the local namespace made
the whole inventory "unsupported or malformed" and held every staging
entry, including the automatic end-of-run recovery.

Accept the empty root and skip it during source matching; it names no
staging source and must not be resolved against the process cwd.
This commit is contained in:
Peter Steinberger 2026-09-27 13:11:19 -07:00 • committed by GitHub
parent 687ab1d101
commit 69c798e965
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 2 deletions

View file

@ -30,7 +30,9 @@ const inventorySchema = z.object({
.array(
z.object({
leaseId: z.string().min(1).max(512),
repoRoot: pathSchema,
// Crabbox records provider resources not yet attached to a repository
// with an empty root, and never clears an attached root without reclaim.
repoRoot: z.union([z.literal(""), pathSchema]),
}),
)
.max(10_000),
@ -304,7 +306,7 @@ export async function verifyNoStagingClaims(params: {
if (Date.now() >= deadline) {
throw new ClaimInventoryHold("Native claim inventory exceeded its inspection budget.");
}
if (within(sourceRoot, directoryLocation(claim.repoRoot).directory)) {
if (claim.repoRoot && within(sourceRoot, directoryLocation(claim.repoRoot).directory)) {
matches.add(claim.leaseId);
if (matches.size === 16) {
break;

View file

@ -172,6 +172,27 @@ it("returns only bounded matching lease IDs for source descendants and canonical
}
});
it("ignores native claims not attached to any repository", async () => {
const context = fixture();
nativeResponse(
output([
{ leaseId: "cbx_unattached", repoRoot: "" },
{ leaseId: "cbx_neighbor", repoRoot: context.source + "-neighbor" },
]),
);
expect(await verifyNoStagingClaims(context)).toEqual({ ok: true });
nativeResponse(
output([
{ leaseId: "cbx_unattached", repoRoot: "" },
{ leaseId: "cbx_stage", repoRoot: context.source },
]),
);
expect(await verifyNoStagingClaims(context)).toMatchObject({
ok: false,
matchingLeaseIds: ["cbx_stage"],
});
});
it.each([
["nonzero partial inventory", output(), 2],
[