Skip to content

fix: vendor hosted patches directly from lockfiles - #1648

Open
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
mainfrom
fix/vendor-lockfile-clean-checkout
Open

Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
mainfrom
fix/vendor-lockfile-clean-checkout

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

socket-patch vendor could ignore hosted lockfile pins when an agent manifest existed, and an unrelated Python environment could reject a pinned patch as not installed. Select hosted patches from the lockfiles first, download their prebuilt artifacts into .socket/vendor/, and rewrite the dependency files without requiring installed packages.

Reuse the existing lockfile readers and vendoring backend. Hosted conversion skips installed-package discovery and a redundant wiring scan, while reusing the restored-lock preview and failure rollback. Preserve the legacy manifest fallback when no hosted pins are in scope, and update CLI help and documentation.

Validation on current main (dc0dc755d):

  • 334 focused tests passed: 71 vendor unit tests and 263 integration/parser tests covering clean npm/Cargo/Python checkouts, ambient virtualenvs, Bun locks, pnpm refusals, dry runs, revert, and rollback.
  • cargo clippy --workspace --all-features -- -D warnings passed.
  • cargo fmt --all -- --check and git diff --check passed.

/code-review high ran on ee978d6 (burn-down agent). Fixed in 840e46b: a pin the restore refused no longer gets a second backend failed event, and CLI_CONTRACT.md no longer lists plain vendor as emitting vendor_would_revert_redirect. Not fixed here, with reasons:

  • Manifest patches are skipped, --offline refuses, and hosted_wiring_contested blocks whenever a hosted pin is in scope, even with a manifest: this is the maintainer's chosen behavior (a) (vendor ejects only the hosted lockfile pins). A warning naming the skipped manifest patches would be a reasonable follow-up.
  • The eject preview downloads each prebuilt archive once in the dry backend pass and again on apply: a performance cost, not a correctness bug; caching the verified archive across the two passes is a separate change.
  • The backend preview is skipped for every pin when any restore file is uncaptured (e.g. gradle hosted-index.tsv mixed with a pnpm catalog: pin), so dry and wet runs can disagree in that mix: rare, and the wet run still rolls back cleanly.
  • hosted_inventory now runs before manifest-driven vendoring (an extra lockfile parse), eject: Option<&mut EjectCapture> also switches off discovery, and the vlt heal-target lookup is duplicated three times: maintainability cleanups, left for a follow-up.

Note

Medium Risk
Changes default vendor behavior when hosted pins and an agent manifest coexist, with all-or-nothing eject rollback; extensive new tests mitigate correctness risk.

Overview
Plain socket-patch vendor now treats hosted lockfile pins as the source of truth, even when .socket/manifest.json exists. It runs a single eject (restore upstream → download prebuilt patches → rewire locks) for every in-scope hosted pin, without requiring node_modules/site-packages and without using installed-package discovery or manifest-selected UUIDs. Manifest-only patches are skipped (unchanged on disk); vendoring those still requires scan --mode vendored.

The eject path fetches each pinned patch from the Socket API, previews backend refusals against restored lock text in dry-run (eject_planned / eject_refused instead of per-purl vendor_takeover_reverted_redirect), refuses --offline up front (offline_eject_unavailable), and on success heals stale vlt store copies like a takeover. The vendored backend skips a second hosted-pin discovery when eject is set.

CLI help, CLI_CONTRACT.md, docs/usage.md, and integration tests are updated across npm, Bun, Cargo, PyPI, sbt, and fresh-checkout scenarios.

Reviewed by Cursor Bugbot for commit 840e46b. Configure here.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] I ran /code-review high on 25a3728, which AGENTS.md requires before this leaves draft. One finding needs a product decision before this PR can be ready, so I'm leaving it as a draft and labelling it agent:needs-human.

Question for a human: when .socket/manifest.json exists and at least one hosted pin is in scope, should vendor (a) vendor only the hosted lockfile pins, as it does now, (b) vendor the union of the manifest and the hosted pins, or (c) refuse and name the manifest patches it would skip?

As written (vendor.rs:991-1017), vendor returns from run_eject(pins) before the manifest arm runs. Any manifest patch without a hosted pin is skipped silently and the run exits 0. Example: the manifest lists pkg:npm/a@1 applied in place, and package-lock.json pins pkg:npm/b@1 hosted. Before this PR both were vendored; now only b is, and a fresh checkout builds a without its patch. Related outcomes that follow from the same choice:

  • A contested hosted entry now refuses the run (hosted_wiring_contested) before the manifest check. That blocks manifest-only vendoring even when the contested package is not in the manifest.
  • If the manifest and the lockfile pin the same purl at different UUIDs, the selection flips between runs. Run 1 ejects the lockfile UUID; run 2 has no hosted pins left, takes the manifest arm and re-vendors the manifest UUID.
  • --vex after an eject still reads the existing manifest, so manifest patches that aren't installed can turn a successful eject into exit 1.

Other findings for the author, not verified in depth:

  1. run_eject runs the takeover_dry_refusal(..., Some(&service), ...) preview for every pin on wet runs too (vendor.rs:1844). Each preview fetches the archive, so every artifact is granted twice, and a --dry-run that used to make no service requests now downloads every archive.
  2. The eject path never collects vlt_heal::lock_targets, so a vlt project whose .vlt store still holds the hosted bytes no longer gets healed and gets no redirect_vlt_reinstall_required warning.
  3. Installed-package discovery is skipped under eject. A pypi release with two hosted ?artifact_id= variant pins now always fails vendor_variant_ambiguous; before, the installed variant decided it.
  4. takeover_dry_refusal returns None if any file in the shared multi-pin plan has no staged text (for example bun.lockb). One such pin turns off the preview for every pin.
  5. The doc comment on run_eject still describes a per-package takeover that the eject no longer runs.

CI on the current head is green for the draft subset (6 passed, 31 skipped). The full matrix and Bugbot haven't run yet.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Burn-down agent: I ran /code-review high on 25a37284 (AGENTS.md requires it before this leaves draft). This needs a product decision before anyone fixes code, so I'm adding agent:needs-human and leaving the draft open.

Question: when .socket/manifest.json exists and the lockfiles have at least one hosted pin, should plain vendor:

  • (a) eject only the hosted pins and stop, as this PR does now, or
  • (b) vendor the union, like main does today: hosted pins from the lockfile, plus manifest patches that have no hosted pin?

With (a), manifest patches that have no hosted pin are skipped. The run still exits 0, so a fresh checkout installs those packages unpatched. Projects that use a manifest also hit new refusals: hosted_wiring_contested, offline_eject_unavailable, and patch_fetch_failed for local-only records.

Confirmed failures (local cargo test -p socket-patch-cli --test mode_migration_cargo on this head: 27 passed, 3 failed):

  • double_takeover_a_b_a_preserves_lock_originals
  • hosted_then_vendored_takeover_leaves_pure_vendored
  • vendor_over_unrestorable_hosted_pin_is_refused

The review also flags the same takeover expectations in mode_migration_npm.rs:621 and e2e_golang_hosted_state.rs:534. CI shows green only because every test job is skipped while the PR is a draft.

Other review findings to handle once (a) or (b) is chosen:

  • The takeover_dry_refusal preflight downloads each prebuilt artifact, including on --dry-run. A wet run then downloads it a second time.
  • The dry-run preview drops PENDING/UNAVAILABLE backend codes. That gives eject_planned on the dry run but eject_incomplete on the wet run.
  • CLI_CONTRACT.md:176 and the run_eject doc comment still describe the per-purl takeover.
  • discover_wiring now runs twice on the manifest path.
  • In vendor_records_reusing, whether EjectCapture is present now quietly turns off discovery, alias resolution and takeover. An explicit flag would be clearer.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Question for a human: when .socket/manifest.json exists and at least one hosted pin is in scope, should vendor (a) vendor only the hosted lockfile pins, as it does now, (b) vendor the union of the manifest and the hosted pins, or (c) refuse and name the manifest patches it would skip?

(a) is the correct approach for now. If a user wants to vendor the manifest patches they should do a full scan with vendor mode and apply the edits in the lockfiles.

A plain `vendor` with a hosted pin in scope now ejects exactly the
lockfile's hosted patch, even when `.socket/manifest.json` names another
patch for the same purl; vendoring manifest patches takes a full
`scan --mode vendored`. The hosted→vendored and A→B→A tests expected the
old per-purl takeover onto the manifest's patch.

Point the eject at the mock API, serve the granted crate (with the
sha512 the artifact download checks), and assert the new contract: the
hosted UUID is vendored and attested, the manifest-only UUID is neither
vendored, reported nor attested, and the manifest is left byte-identical.
The fail-closed test now covers both refusals: `--offline` is refused up
front (`offline_eject_unavailable`), and an index that cannot re-resolve
the crates.io entry still yields `redirect_revert_failed` with the
checkout remedy, nothing touched.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: picked this up now that the product question is answered (17:43 comment: (a), vendor ejects only the hosted lockfile pins). No code pushed this run; here is the state for whoever continues.

Why CI is red on 25a3728 (5 jobs: coverage, yarn-classic, cargo lock-v4, bun e2e, ci-ok): the existing takeover tests encode the old behavior. They stage a manifest patch (UUID_V) over a live hosted pin (UUID_H) and expect vendor to do the per-package manifest takeover. With (a), vendor goes straight to run_eject, fetches the hosted record for UUID_H, and the mocks don't serve it, so it refuses with eject_refused: not every hosted patch record could be fetched.

Reproduced locally on this head:

  • mode_migration_npm: classic_hosted_then_vendored_takeover_round_trips_to_registry
  • mode_migration_bun: bun_dry_run_previews_match_wet_outcomes, bun_hosted_then_vendored_takeover_round_trips_to_registry
  • mode_migration_cargo: double_takeover_a_b_a_preserves_lock_originals, hosted_then_vendored_takeover_leaves_pure_vendored, vendor_over_unrestorable_hosted_pin_is_refused
  • mode_migration_pypi: pipenv_hosted_to_vendored_keeps_the_requirements_patched, pipenv_vendor_wires_the_exported_requirements_too, requirements_editable_beside_hosted_pin_unwinds, requirements_sole_hosted_pin_unwinds
  • mode_migration_vlt and vendor_eject pass. coverage reports about 22 failures in total, so other suites are affected too (e2e_golang_hosted_state.rs:534 was flagged earlier).

Next step: rewrite each of these as an eject. Mount the hosted record endpoint for UUID_H, and assert that UUID_H ends up vendored, that a manifest-only patch is left alone, and that --revert round-trips. Don't keep the UUID_V takeover expectations: (a) makes them obsolete. Then work through the earlier /code-review high findings, which still apply: the takeover_dry_refusal preflight double-downloads (including on --dry-run); PENDING/UNAVAILABLE codes are dropped in the dry preview; vlt_heal targets aren't collected on eject; pypi ?artifact_id= variant ambiguity; the bun.lockb staged-text gap disables the preview for every pin; and the stale docs (CLI_CONTRACT.md:176, the run_eject doc comment).

Not labeled; state set to ready so the next run continues.


Generated by Claude Code

With a hosted pin in scope, plain `vendor` ejects the lockfile's hosted
patch and ignores `.socket/manifest.json`; the bun hosted→vendored and
dry-run tests still expected the manifest's patch (uuid_v) to take over
the hosted pin, with the per-purl takeover advisories.

Point `vendor_cmd` at the mock API so the eject can fetch the record,
expect the hosted uuid to be vendored (detached ledger entry, VEX on the
view's CVE), and assert that the manifest-only patch is not previewed,
reported or vendored and that the manifest is left byte-identical. The
dry run now previews the eject (`eject_planned`) instead of the old
`vendor_would_revert_redirect` advisory.

Co-Authored-By: Claude <noreply@anthropic.com>
The classic and berry hosted→vendored legs expected plain `vendor` to
take over the hosted pin with the staged manifest's patch (UUID_V) and
announce `vendor_takeover_reverted_redirect`. With a hosted pin in scope
`vendor` now ejects the lockfile's hosted patch and ignores the manifest.

Point the vendor runs at the mock API (the eject fetches the record from
`patches/view`), expect UUID_H and its CVE in the manifest-less VEX
matrices with UUID_V as the never-attested patch, and replace the
takeover-advisory check with the eject contract: the manifest-only patch
is neither reported nor vendored, and the manifest is not rewritten.

Co-Authored-By: Claude <noreply@anthropic.com>
The test staged a manifest naming UUID_V over a hosted go.mod pin and
expected plain `vendor` to take the pin over with the manifest's patch.
With a hosted pin in scope `vendor` now ejects the lockfile's hosted
patch (UUID_H) and ignores the manifest; the run reached the public
proxy for the record and failed.

Point the run at the mock API, serve the hosted patch's prebuilt module
from the artifact-service mock, and expect the go.mod replace to name
UUID_H. Assert the manifest-only patch is neither reported nor vendored
and the manifest is left byte-identical; the existing upstream-restore
checks (no hosted replace, go.sum back to its pre-redirect bytes, no
ledger) are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
The "Takeover reconciliation" contract and the `run_eject` doc comment
still described plain `vendor` as a per-purl takeover onto the
manifest's patches. With a hosted pin in scope it ejects exactly the
lockfile's hosted pins as one planned transition and ignores the
manifest; say so, point manifest vendoring at `scan --mode vendored`,
and keep the per-purl takeover wording for `scan` / `get --mode
vendored` and the no-hosted-pin manifest path.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: following the maintainer's answer (keep (a): vendor only the hosted lockfile pins), I pushed bdce77b. It changes tests and docs only; product behavior is unchanged.

  • mode_migration_cargo, mode_migration_bun, mode_migration_npm and e2e_golang_hosted_state now expect (a):
    • The hosted pin (UUID_H) is vendored and attested.
    • The manifest-only patch (UUID_V) is never vendored or reported.
    • The manifest is left untouched.
    • The vendor runs point at the mock API. For cargo, the granted artifact is served with sha512.
  • vendor_over_unrestorable_hosted_pin_is_refused now checks both refusals: offline_eject_unavailable for --offline and redirect_revert_failed online.
  • CLI_CONTRACT.md and the run_eject doc comment now describe the eject instead of the per-purl takeover.
  • Local results: cargo 30/30, bun 35/35 (bun 1.4.2), npm 32/32 (yarn 1.22.22), golang 29/29.
  • Not run locally: the Berry leg (no yarn 4 here) and the cargo lock-v4 leg.
  • Not checked: the earlier coverage log listed ~21 more failing tests that I couldn't see, so CI on this head is the real check.

Generated by Claude Code

Plain `vendor` with hosted pins in scope now ejects only the lockfile
pins and fetches each pinned record from the patch API, so tests that
staged a manifest and expected the per-purl takeover failed with
`patch_fetch_failed` (coverage job). Point those runs at mock API
records (sbt, pypi requirements/pipenv, bun, vlt, pnpm/npm conversion,
berry) and assert the eject contract: `eject_planned` on dry runs,
`eject_refused` / `offline_eject_unavailable` for refusals, the hosted
pin's UUID vendored, and the manifest left untouched. The bun prune test
now builds its manifest-tracked entry from a registry lock, since only
manifest vendoring without hosted pins produces one. The help hygiene
test expects the new one-line vendor summary.

The vlt dry/wet test exposed a product gap: the eject skipped the warm
vlt store heal the per-purl takeover runs, leaving the hosted store copy
and hidden lock behind, so a later `vendor --revert` could keep patched
bytes under a registry pin. The eject now heals those store entries once
it stands and reports `redirect_vlt_reinstall_required`.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: pushed ee978d6 to fix the coverage failures on bdce77b. Behavior stays (a): vendor ejects only the hosted lockfile pins.

  • 26 failing tests in 5 targets. All pass on main, so every failure came from this PR. 25 of them failed because vendor now ejects and fetches the pinned record from the API, which the test mocks didn't serve. These are now rewritten as eject tests: the record endpoint is mocked, they expect eject_planned / eject_refused / offline_eject_unavailable, and they check that the manifest is left untouched. The targets are in_process_vendor (9), in_process_vendor_bun_takeover (10), mode_migration_pypi (4) and e2e_sbt_vendor (2). The last one is help_text_hygiene, which now matches the new vendor summary.
  • Product fix in run_eject. The eject never cleaned up vlt's installed copies of the hosted packages, so a later vendor --revert could leave patched bytes under a plain registry pin. It now collects vlt_heal::lock_targets before the restore. Once the eject succeeds, it heals them and reports redirect_vlt_reinstall_required, as the per-purl takeover did. This was review finding 2 from the earlier /code-review high.
  • Local results. The 5 failing targets and 10 related ones pass (vendor_eject*, mode_migration_, in_process_vendor__takeover), along with the 71 commands::vendor unit tests. cargo clippy -p socket-patch-cli --all-features -D warnings is clean, and rustfmt was run only on the files I touched.
  • Still open from the earlier review, not addressed here: the takeover_dry_refusal preflight downloads each archive twice, including on --dry-run; PENDING/UNAVAILABLE codes are dropped in the dry preview; pypi ?artifact_id= variant pins are ambiguous; and a bun.lockb with no staged text turns off the preview for every pin.

Waiting on CI and Bugbot for ee978d6.


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

A pin whose upstream restore was refused already has its failure event.
Previewing the backend over a lock where that pin is still hosted added a
second `failed` event for the same purl with a backend code, which double
counted summary.failed and blamed the wrong step.

Also stop listing plain `vendor` as a source of
vendor_would_revert_redirect in CLI_CONTRACT.md: with a hosted pin in
scope it ejects and reports eject_planned / eject_refused instead.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 840e46b. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 21, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review (burn-down agent).

  • Head: 840e46b600351be74b2aa846a9f9b9f97e41f027, mergeable with main.
  • CI: 35/35 checks green on this head (19 skipped by tier).
  • Bugbot: reviewed 840e46b, no findings.
  • /code-review high ran on ee978d6. 840e46b fixes two findings: a pin whose restore was refused got a second backend failed event, and a stale CLI_CONTRACT row. The PR description explains why the other findings were not fixed here. Most of them follow from the chosen behavior (a); the rest are perf or cleanup follow-ups.
  • Reviewer focus: plain vendor now ignores .socket/manifest.json whenever a hosted pin is in scope, with no warning about the skipped manifest patches. --offline and hosted_wiring_contested now refuse in that case too.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review: needs a product decision before approval

Final review at 840e46b6. CI is green and the code is careful, but this PR changes what plain vendor does for users who have both .socket/manifest.json and hosted lockfile pins. It needs an explicit sign-off rather than shipping as a fix:.

The change. On main, vendor ejects only when there is no manifest. Here, any hosted pin in scope sends the run to run_eject, which never reads the manifest (vendor.rs:991-1024).

What that does to an existing user

  1. Manifest patches are dropped silently. Say the manifest holds patch A for a package that isn't hosted-pinned, and the lockfile hosted-pins B. Main vendors A and takes over B. This PR vendors only B, exits 0 and warns about nothing. The result also depends on scope: vendor --ecosystems <one without hosted pins> still takes the manifest path.
  2. reconcile_dropped / vendor_reconciled no longer runs while any hosted pin is in scope. It lives only in run_vendor, so a manifest-tracked entry whose patch was removed is not reverted. After a successful eject the next vendor takes the manifest path again, so if the manifest names a different UUID for an ejected purl, two runs can give two results. I traced this but did not run it, and no test runs vendor twice in this setup.
  3. JSON and exit-code changes for these users:
    • dry run: vendor_would_revert_redirect becomes eject_planned;
    • --offline now refuses the whole run with offline_eject_unavailable, where before it still vendored the non-hosted patches;
    • restore refusals are now all-or-nothing (eject_refused);
    • an invalid manifest no longer errors when hosted pins exist;
    • the eject now honours --force.
  4. CLI_CONTRACT.md contradicts itself.
    • Line 176 now says plain vendor ejects and ignores the manifest, but the same paragraph still describes vendor_would_revert_redirect / redirect_revert_failed.
    • Lines 942-943 still say standalone vendor is fed by the manifest "or, with no manifest, by the lockfiles' hosted pins".

Questions for the owner

  • Should hosted pins override an existing manifest for plain vendor?
  • If yes: should manifest patches that won't be vendored get a warning or a refusal, and should reconcile_dropped still run?
  • Should vendor --offline with a manifest keep vendoring the non-hosted patches?
  • Does this need a contract or major-version note, and the contract text above fixed?

Verified

  • cargo test --test vendor_eject --test vendor_eject_fresh_checkout --test help_text_hygiene --test in_process_vendor --test vendor: all pass.
  • The new tests fail on main.
  • CHANGELOG.md is untouched.

Labelled agent:needs-human. I posted no review brief, so auto-merge stays off.


Generated by Claude Code

// Hosted pins are the input, even when an agent manifest also exists.
// The raw lockfile inventory does not require VEX eligibility or an
// installed copy of a patch. Revert needs only the vendor ledger.
if !args.revert {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[agent] The hosted_wiring_contested refusal now runs for manifest projects even when no hosted pin is in scope, so the "preserve legacy manifest-only vendoring when there are no hosted pins" fallback is unreachable whenever any contested hosted ref exists anywhere in the tree (hosted_inventory isn't ecosystem-scoped).

Scenario: a project with .socket/manifest.json runs vendor --ecosystems pypi while an unrelated npm package.json holds a lockless hosted URL (contested). Main vendors the manifest patches; this head exits 1 with hosted_wiring_contested and vendors nothing. The same happens with zero attributable pins and one contested ref.

Fix: when the manifest exists, check contested_refusal() only inside if !pins.is_empty(), so that with no in-scope pins the run falls through to the manifest arm as before.

@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 22, 2026
Resolve the vendor_eject.rs conflict with #1646 by keeping both new
tests: this PR's manifest-plus-hosted-pins eject test and main's
user-patch consent test for the eject path.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: merged main (5d151e3374) into this branch at 090167a to clear the merge conflict. The only conflict was in tests/vendor_eject.rs, where #1646 added a user-patch consent test at the same spot as this PR's manifest test. I kept both tests. Locally, vendor_eject, vendor_eject_fresh_checkout, in_process_vendor, vendor and help_text_hygiene all pass, and so does cargo clippy --workspace --all-features -D warnings. I removed Ready for review until CI is green on the new head. The agent:needs-human product question above is still open.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants