Repository navigation
computer: Send Git's network requests through a host fetch - #245
mattzcarey wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: e43b0bd The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
|
@ask-gardener can you look at whether this should be implemented as a custom fetcher instead. Ideally we'd just provide the fetch implementation directly, this would allow fetchers to be passed down the stack. |
|
@aron-cf Yes—after tracing the client, I recommend an injected fetch implementation rather than a Git-specific auth resolver.
|
|
@ask-gardener can you implement that on this branch? Or alternatively take this PR, make those changes and implement a fresh PR. |
|
@aron-cf Implemented the custom Fetch approach directly on
|
|
@ask-gardener can you resolve the merge conflicts and if possible update the PR description |
The Git commands the model runs, the worker-shell `git` command and codemode:git, had no way to authenticate. Host code could pass `headers` or `onAuth` on each typed call, but a model running `git push` against a private repository had to put a token on the command line, where it ends up in the transcript. createGitClient() now takes `fetch`. Every request a clone, fetch, pull or push makes goes through it, from the typed API, cli(), the shell command and codemode:git alike, since the last two already run through the host's Git client. A wrapper can add credentials, which then never reach argv, the remote URL, .git/config or output, and a service binding's fetch can be passed down as it is. Which requests get credentials, whether a caller's Authorization header wins, and how redirects are handled stay with the wrapper; the docs show one that scopes a token to a GitHub organisation and won't follow a redirect with it. A small adapter bridges isomorphic-git's iterable request and response bodies to Fetch streams, so packfiles aren't buffered, and cancels the response when Git stops reading. Without `fetch`, Git uses isomorphic-git's own transport as before.
c466d28 to
e43b0bd
Compare
| // Required by Node's Fetch for streaming uploads; ignored by Workers. | ||
| init.duplex = "half"; | ||
| } | ||
| const response = await fetcher(url, init); |
There was a problem hiding this comment.
🟡 Failed fetch leaves upload streams open
When a host fetch rejects a Git POST, withFetch leaves its upload stream uncancelled. The stream can already have pulled from the pack iterator, leaving it open after the failed push.
Learn more
A Git push supplies an iterable pack body, and the adapter wraps it in a ReadableStream that can pull before the host fetch settles. If the supplied fetch rejects without consuming that body, the adapter returns immediately; its cancel() handler never runs and the underlying iterator never receives return(). This is distinct from cancellation of the response stream, which only exists after fetch resolves.
Example: A host fetch wrapper rejects a POST while refreshing its token. The stream has pulled its first pack chunk, but the rejected push never closes the pack iterator.
Recommended fix: Hold the upload stream separately and cancel it when fetcher(url, init) rejects. Cover rejection before and after the upload begins with a generator that records its cleanup.
Was this helpful? React with 👍 or 👎 to provide feedback.
| * credentials, whether a caller's `Authorization` header wins, and how | ||
| * redirects are handled. Defaults to isomorphic-git's own transport. | ||
| */ | ||
| fetch?: typeof globalThis.fetch; |
There was a problem hiding this comment.
🟡 New Git option lacks consumer example
The new fetch option changes the public createGitClient API, but no runnable example uses it. Repository guidance requires updating examples alongside public API changes.
Learn more
The repository treats its examples as real consumers of the public API and explicitly requires updating them when that API changes. The new option is demonstrated in documentation, but no runnable example exercises it.
Example: A developer starting from the worker-shell example sees createGitClient() without a host fetch and cannot use its shell Git command against a private remote without configuring authentication separately.
Recommended fix: Update a Git-enabled example to pass a scoped host fetch wrapper and demonstrate the new option in a runnable consumer.
Was this helpful? React with 👍 or 👎 to provide feedback.
| request.headers.set("Authorization", "Bearer host-token"); | ||
| } | ||
| requests.push(request); | ||
| return new Response(null, { status: 401, statusText: "Unauthorized" }); |
There was a problem hiding this comment.
🔍 Successful Git transfers remain untested
All Workspace cases stop at a 401 discovery response, before any pack transfer. The upload test uses Response to consume bytes rather than sending a Fetch request. A successful clone or push against a local HTTP server would exercise the missing integration.
Was this helpful? React with 👍 or 👎 to provide feedback.
The Git commands the model runs, the worker-shell
gitcommand andcodemode:git, have no way to authenticate (#218). Host code can passheadersoronAuthon each typed call, but a model runninggit pushagainst a private repository has to put a token on the command line, where it ends up in the transcript.createGitClient()now takes afetch, and every request a clone, fetch, pull or push makes goes through it. That covers the typed API,cli(), the shell'sgitcommand andcodemode:git, since the last two already run through the host's Git client. A host wrapsfetchto add credentials, which then never reach argv, the remote URL,.git/configor the output:An earlier version of this change added a Git-specific
authresolver. Taking a plainfetchinstead keeps the policy with the host: which repositories get a token, whether a caller'sAuthorizationheader wins, and how redirects are handled. It also lets a service binding'sfetchbe passed straight down. A host that wants read-only tokens for fetches and write tokens for pushes can tell them apart by the Git service in the URL (git-upload-packorgit-receive-pack). Request and response bodies are streamed, so packfiles aren't held in memory. Withoutfetch, Git uses isomorphic-git's own transport as before.http.test.tscovers the streaming adapter: uploads, status and headers, empty responses, cancelling when Git stops reading, and fetch failures. It also drives a realWorkspacethrough a typed clone, a CLI clone (checking the token isn't in the output), and typed fetch, pull and a CLI push, using afetchthat adds a token and answers 401.npm test --workspace @cloudflare/computer -- src/git/http.test.tsThe "Host Fetch and authentication" section of
docs/13_git_interface.mdand the README's Git section describe the option. A minor changeset is included.