Skip to content

feat(prisma-cloud): resolve the deploy target by project logical id - #230

Merged
kristof-siket merged 5 commits into
mainfrom
feat/prisma-cloud-slug-resolution
Aug 15, 2026
Merged

feat(prisma-cloud): resolve the deploy target by project logical id#230
kristof-siket merged 5 commits into
mainfrom
feat/prisma-cloud-slug-resolution

Conversation

@kristof-siket

@kristof-siket kristof-siket commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What changes

`resolveProject` in `packages/1-prisma-cloud/0-lowering/lowering/src/container.ts` previously found a project by matching its display name against the Composer module name, then adopted the oldest match when duplicates existed.

This PR changes the resolution order:

  1. Logical id match (preferred): if any project in the workspace has a `logicalId` field equal to the module name, that project is adopted immediately. Logical id is workspace-unique and rename-proof -- it is set at create time and never changes.
  2. Name fallback (unchanged): if no logical id match exists, the code falls back to display-name matching, oldest-first, exactly as before.

Why display names were the wrong key

Display names are mutable and not guaranteed to be unique within a workspace. A user can rename a project after its first deploy, causing the next deploy to create a new project instead of targeting the existing one. The logical id is a module-name-format identifier (same constraints as Composer's `module()` name), set once at creation and tied to the workspace, which makes it a stable deploy identity.

Backward compatibility

The platform does not return a `logicalId` field on projects today. With the current API, every project has `logicalId: null` or the field is absent, so the logical id match finds nothing, the name fallback runs, and behavior is byte-identical to what was there before. Once the platform ships the `logicalId` field, new projects get the stable identity automatically -- no deploy-side changes needed.

Logical id on create

`POST /v1/projects` now includes `logicalId: appName` in the body. Today's API ignores unknown fields, so this is a no-op against the current platform. Once the platform ships `logicalId` support on the create endpoint, the logical id is set on the first deploy.

409 conflict surface

A `409` from project create means the requested name is already owned by a different project in the workspace. The error message tells the user to rename the Composer module or free the name. Silently adopting the conflicting project is not safe -- it would deploy into someone else's project.


Generated with Claude Code

E2E proof

scripts/logical-id-resolution.ts runs in the logical-id-resolution CI job (wired to pull_request in e2e-deploy.yml).

Scenario 1 — rename-proof adoption: creates a project with a unique logical id, renames its display name to something unrelated, then calls resolveContainer with the original logical id. The test asserts the resolver returns the original project id and that no new project was created. Display-name matching alone would miss the renamed project and create a duplicate, so only a successful logical-id match can pass this assertion.

Scenario 2 — duplicate rejection: attempts a second POST /v1/projects with the same logical id. The test asserts the platform returns 409, proving uniqueness is enforced at the API level.

Cleanup runs in a finally block; the resolver prefix is also covered by the sweep in the last CI job so failed runs do not leak projects.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b851dcba-322a-4321-8732-028efb1606e0

📥 Commits

Reviewing files that changed from the base of the PR and between cc0c006 and 916c452.

📒 Files selected for processing (4)
  • .github/workflows/e2e-deploy.yml
  • packages/1-prisma-cloud/0-lowering/lowering/src/__tests__/container.test.ts
  • packages/1-prisma-cloud/0-lowering/lowering/src/container.ts
  • scripts/logical-id-resolution.ts

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/prisma/composer/@prisma/composer@230
npm i https://pkg.pr.new/prisma/composer/@prisma/composer-cli@230
npm i https://pkg.pr.new/prisma/composer/@prisma/composer-prisma-cloud@230

commit: 916c452

@kristof-siket
kristof-siket force-pushed the feat/prisma-cloud-slug-resolution branch 2 times, most recently from 31043c8 to 146b410 Compare August 13, 2026 15:14
wmadden-electric added a commit that referenced this pull request Aug 13, 2026
The module name is the project logicalId and every node address is that
node logicalId — the identity the topology submits. Project-level
resolution is PR #230; the per-resource stamping waits on the platform
columns. Records the two hazards: the wire-field rename (slug →
logicalId) is free only while #4885 and #230 are both unmerged, and the
lowering-address root-segment question must be verified against the
topology addresses before stamping, or the join fails on a prefix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kristof Siket <siket@prisma.io>
@kristof-siket
kristof-siket force-pushed the feat/prisma-cloud-slug-resolution branch from 146b410 to 2916c4a Compare August 14, 2026 09:26
@kristof-siket kristof-siket changed the title feat(prisma-cloud): resolve the deploy target by project slug feat(prisma-cloud): resolve the deploy target by project logical id Aug 14, 2026
@kristof-siket
kristof-siket marked this pull request as ready for review August 14, 2026 12:21
kristof-siket and others added 3 commits August 14, 2026 14:21
…ucture

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kristof Siket <siket@prisma.io>
Signed-off-by: Kristof Siket <siket@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kristof-siket

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristof-siket

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristof-siket

Copy link
Copy Markdown
Contributor Author

@coderabbitai review 🥺

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

@kristof-siket I will review the changes in #230.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristof-siket

Copy link
Copy Markdown
Contributor Author

Review pass (Claude Code, time-boxed)

Checked against the diff on branch feat/prisma-cloud-slug-resolution (four files: container.ts, container.test.ts, scripts/logical-id-resolution.ts, .github/workflows/e2e-deploy.yml).


resolveProject — logical-id precedence and fallback parity
The logicalIdMatch check runs first and returns early; the display-name path is reached only when no logicalId match exists. The name-match logic (filtersort[0]) is functionally identical to the pre-PR code — workspace pre-filter was pulled out as a named variable, not changed. When logicalId is absent from an older API response (undefined), p.logicalId != null evaluates to false, so the name fallback applies exactly as before. No findings.

Create path — logicalId field and 409 scoping
logicalId: appName is sent on POST. The Effect.catch is piped onto the specific call(() => client.POST('/v1/projects', ...)) Effect only; no other operation in resolveProject can produce a 409, so the handler is correctly scoped. The error copy ("a project with this name already exists in the workspace; rename your Composer module or free the name.") is plain text and CI-log-friendly. The Effect.catch((err) => …) pattern follows the pre-existing convention at line 196 in the same file. No findings.

One design note (not a blocker): resolveBranch documents and handles the concurrent-creation race explicitly — a 409 on branch create triggers a re-lookup rather than failing. resolveProject does not retry on 409; a tight race between two concurrent deploys creating the same project would surface the "already exists" error to the user. Pre-PR behaviour was a raw 409 PrismaApiError, so this is strictly better, but the asymmetry with resolveBranch is worth noting for follow-up.

e2e script — cleanup, uniqueness, secrets
Cleanup runs in finally; createdProjectId is set before any destructive step, so the block is safe on early exit. logicalId = 'resolver-ci-${runId}' using GITHUB_RUN_ID (unique per GitHub Actions run) or process.pid for local runs — no collision risk across concurrent runs. The token is read into a module-scope variable and never logged; only logicalId and project IDs appear in console.log. No findings.

Workflow wiring
timeout-minutes: 5 is set. sweep-prefixes has resolver added, which covers leftover resolver-ci-* projects. The if: guard (github.event.pull_request.head.repo.full_name == github.repository) prevents fork PRs from accessing secrets. The job runs inside the repo-wide e2e-deploy concurrency group (cancel-in-progress: false), consistent with the other jobs in this workflow. One cosmetic nit: name: Logical-id resolution (prisma/composer#230) bakes in the PR number — that reference goes stale after merge. Not a blocker.

Tests
The fake client echoes logicalId from the POST body correctly (typeof init.body?.['logicalId'] === 'string' ? init.body['logicalId'] : null). All four new tests assert the semantics named in their descriptions: logical-id match wins over a display-name match, name fallback works when no logicalId match exists, creation sends the module name as logicalId, and 409 on create surfaces the expected error. No findings.

"slug" vocabulary
'proj-slug' appears in test fixture IDs — these are opaque ID strings, not concept references. No leftover "slug" terminology referring to the renamed concept. No findings.


Claude Code — time-boxed low-effort review pass

Signed-off-by: Kristof Siket <siket@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kristof-siket

Copy link
Copy Markdown
Contributor Author

@coderabbitai approve

(it has been rate limited for hours so i did a /code-review skill review as replacement)

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

@kristof-siket Approved. The review summary reports no blocking findings.

✅ Action performed

Comments resolved and changes approved.

@kristof-siket
kristof-siket merged commit af1f737 into main Aug 15, 2026
36 of 38 checks passed
@kristof-siket
kristof-siket deleted the feat/prisma-cloud-slug-resolution branch August 15, 2026 07:14
wmadden pushed a commit that referenced this pull request Aug 17, 2026
* docs(drive): shape the build-reporting project

Design review of the brief against the platform contract in
pdp-control-plane PR #4855 and the Composer deploy pipeline, plus the
project spec, plan and design notes.

The review changed the design in four ways: the workspace comes from the
token so PRISMA_WORKSPACE_ID is not part of the contract; commitSha and
branchName are required and Composer reads no git today; the SDK has none
of the builds endpoints yet; and end-of-run resource reporting could only
ever cover three of the eight platform resource types, so reporting is
intercepted at the state layer instead.

A fifth finding was handed to the author of #4855: the build anchors are
settable only at creation, which no column constrains, so Composer cannot
attach a build to the project it resolves partway through a deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* feat: report every deploy to Prisma Cloud as a Build

Composer is the first external reporter against the platform build API
(pdp-control-plane #4855). A deploy now records that it happened, how far
it got, how it ended, and which platform resources it touched, and can
emit its outcome as JSON for the Prisma GitHub Action.

Three parts:

The run report. `--report <path>`, or PRISMA_COMPOSER_REPORT_FILE, writes
a versioned JSON file carrying the app, its nodes and entities, preview
URLs, and the failure cause. Written on the failure path too, where the
existing internal summary does not exist. Distinct from that summary,
which stays a private per-run carrier the parent deletes.

The build lifecycle. A new `reporter` hook on ExtensionDescriptor, driven
by the CLI: opened after the graph loads and before containers resolve,
so a bootstrap failure that orphans a project (#103) is still recorded;
anchored once the project and branch exist; finished on every exit path
including a thrown defect and a caught signal. It joins PRISMA_BUILD_ID
when the Action supplies one, and otherwise creates the build itself —
`ci` with a run identity under GitHub Actions, `cli` elsewhere. Phase is
always `deploy`: Composer never builds the user code (ADR-0005).

Resource reporting. The state store reports each resource as it lands,
mapping the seven Prisma Cloud Alchemy resources onto the platform types.
The state store is the interception point because the descriptors\x27 entity
vocabulary is three kinds wide against eight platform types, and
`deployment` — the one that makes the platform maintain the build link —
is not an entity at all. Reports fire without blocking the apply and are
drained before the deploy lease is released.

Reporting never fails a deploy. Every call warns and returns instead of
throwing, and the CLI swallows anything a reporter still manages to throw.

Two placement constraints shaped this. The framework may import nothing
but external dependencies, so reporting reaches the CLI through the
extension seam. The extension package may read no environment and import
no node builtin (its invariants 4 and 5), so the session lives in
lowering and the extension keeps a five-line adapter.

Not yet verifiable end to end: the platform routes are unmerged and the
SDK carries none of them, so the three calls are hand-written for now
against the same token and origin, to be deleted when the SDK ships them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* refactor(prisma-cloud): move build reporting onto the shipped SDK

The builds API is in production: pdp-control-plane #4853, #4850 and #4855
all merged, and @prisma/management-api-sdk 1.60.0 carries every endpoint.
So the temporary hand-written client is gone and the three calls go
through the same generated client as every other call in this package.

Every request and response shape is now derived from the SDK\x27s generated
`operations` type rather than restated. A hand-kept copy of a contract
someone else owns drifts silently; a derived one breaks the build. The
derivation was checked with a compiled probe in both directions, since a
type that collapses to `never` or widens to `any` typechecks just as
quietly as a correct one.

The pin moves to ^1.60.0, which also closes a gap that predates this work:
the hosted-state lease and scope endpoints were missing from the client
too, so this package never typechecked cleanly. It does now.

The anchor amendment landed in full — the anchors are fill-only, verified
against what the build already carries, 409 on a genuine change — and it
gained `deployedUrl` on the same terms. So a build now also records the
app it deployed and where it can be reached, but only when the run
deployed exactly one compute service: those columns hold one value each,
and picking a service arbitrarily would imply it was the app\x27s address.
Multi-service apps get neither and lose nothing, since every service is
reported through the resources endpoint regardless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* feat(cli): --build-id joins a deploy record CI already created

PRISMA_BUILD_ID was the only way to hand Composer an existing build, which
suits a runner that exports one id for a whole job but not a workflow that
names a different record per step. `--build-id` adds the other channel and
wins over the variable, because a flag passed for one step means that step.
An empty value counts as unset on both — that is how a shell spells "no
value", and mistaking it for an id would report into nothing.

The framework field is `ReportBeginInput.reportId`, deliberately not
`buildId`: core has no idea what a Build is, and the id is opaque to it.
The flag keeps the platform\x27s own noun because that is what a user copies
out of their CI, and nothing else on this CLI takes a build id — so it
cannot be confused with the build adapter that produces a service bundle
(ADR-0005), even though both are spelt "build".

Deploy only; destroy has no record to join.

Also records what the platform does and does not do about run identity,
having checked it: `sourceEventIdForRun` owns the dedup key, so two
reporters of one CI run converge on a single Build — but the platform reads
no GitHub environment itself. Deriving the identity stays the reporter\x27s
job, which means Composer and the Action must derive it identically or one
run produces two builds. Passing an id down side-steps that entirely, which
is the reason this flag matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* refactor: drop the anchor vocabulary; correct two outdated platform claims

The term came from an older platform doc and is outdated (operator,
2026-08-13): a Build's required scope is its workspace, from the token;
projectId/branchId/appId are ordinary optional foreign keys that narrow
the Console's views. The hook is now `attach`, the extension option
`refsOf`, and nothing says anchor.

Two design-note claims corrected against today's pdp main: the Console
lists builds at workspace level too (#4860), so a build without a project
reference is visible, not lost; and the git webhook now converges with a
CI run's build on the same sourceEventId (#4877), superseding the note
that said the two key spaces never meet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): record the settled topology-persistence design

Outcome of the design discussion on the platform resource model: the
platform holds the branch's full graph — nodes, ports, edges, module
boundary forwarding included — as three generic record types under the
Branch, submitted as one replace-set pre-apply, keyed by config address
with no platform ids in the payload (the platform joins to its typed rows
on branch/kind/key at read). Build Job stays a pure run journal; the two
are deliberately decoupled. Rejected alternatives and the Composer-side
follow-up slices are recorded with the reasoning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): rewrite the project docs for a context-free implementer

Fresh-eyes pass at the operator's direction: lead with the decision,
state the design and name the requirements it satisfies, end with the
alternatives, and stop making the reader reverse-engineer the design
from functional requirements.

topology-design.md is rewritten as the implementation handoff: a
vocabulary table for readers new to either codebase, a precise schema
(endpoint directionality, the $out convention for anonymous resource
outputs, per-consumer-endpoint uniqueness), a worked example, the exact
flatten algorithm, and the pipeline position of the write. The rewrite
also fixes two things review surfaced: the join-by-key rule silently
required typed rows to carry the full config address (now a named
platform change, configKey), and the write cannot precede container
resolution as previously implied, because there is no Branch row to
write under until containers resolve.

spec.md now opens with the shipped design and maps each requirement to
what satisfies it; decision ids are unchanged, ordered logically instead
of chronologically, and the stale pre-amendment contract claim is gone.
design-notes.md is bannered as the chronological log it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): the canonical topology handoff moved to pdp-control-plane

The implementer works in pdp-control-plane, so the spec now lives there
under its projects/ convention (PR #4892); this copy is the mirror kept
for the Composer-side work items.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): mirror the logicalName decision into the topology spec

The join column is logicalName: the declaring node's full topology
address stored verbatim, joined by string equality. Identifier taxonomy:
id (physical, minted), logicalName (declared identity in the logical
namespace the topology maps), displayName (presentation).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): collapse the topology mirror into a pointer

One canonical home for the design (pdp-control-plane
projects/branch-topology/); the composer copy kept drifting. Composer's
own work items move to this project's plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): record the logicalId adoption direction for Composer

The module name is the project logicalId and every node address is that
node logicalId — the identity the topology submits. Project-level
resolution is PR #230; the per-resource stamping waits on the platform
columns. Records the two hazards: the wire-field rename (slug →
logicalId) is free only while #4885 and #230 are both unmerged, and the
lowering-address root-segment question must be verified against the
topology addresses before stamping, or the join fails on a prefix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): the address form is settled — bare of the root prefix

Verified in core: root-scope children get unprefixed addresses; the root
node address is its own name, which is the Project logicalId. The
verify-later note is replaced by the fact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): the live deploy is verified — definition of done met

Build bld_cqdzjjlja99nmcd4f27bn69g in the dev workspace: right source,
phase, state, branch, commit; project attached through the fill-only
PATCH; appId/deployedUrl correctly withheld for a two-service app; 21
resource rows including both deployments. Records the two non-blocking
observations: the Project has no resource row on the hosted flow, and
branchId stays null on default-stage deploys.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* docs(drive): the status header matches the verified DoD

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* fix: address the CodeRabbit review — bounded reporting, enforced contracts

Every reporting request now carries a 10s deadline (AbortSignal.timeout
on each SDK call), so a hung platform connection can stall a deploy by
at most that. The resource drain gains its own 15s backstop: a report
that never settles is abandoned with a warning instead of holding the
state-layer finalizer — and with it the deploy lease — open forever. The
deadline is injectable, and the new test proves the abandon path fires
rather than merely that the drain is pending.

The reporter now enforces its own documented never-throws contract
instead of borrowing it from the CLI wrappers: begin catches everything
into a warning and no session; finish catches the terminal PATCH the
same way. New coverage drives both with a rejecting BuildsApi.

Docs and tests from the same review: the run-report fixture is typed
`satisfies DeploymentSummary` so shape drift fails compilation; the
.drive documents' repo-relative links resolve from their own directory;
and the two claims superseded by the shipped amendment and the live
verification are marked as such instead of contradicting the status
header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

* test: the git-identity test builds its own repo instead of borrowing the checkout

CI checks out pull requests at a detached HEAD, where "no branch" is the
designed answer — so a test asserting the HOST checkout resolves a branch
failed there while passing on any developer machine. It now creates a
temp repository with a named branch and asserts against that, and a new
case pins the detached-HEAD behaviour explicitly: no branch, no identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

---------

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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