Agent skills for Cloudflare, starting with Artifacts: Git repos for agents through the Workers binding, the REST API, and plain git, with type-checked TypeScript and tested Go examples.
100
100%
Does it follow best practices?
Impact
100%
1.00xAverage score across 4 eval scenarios
Low
Low-risk findings worth noting
Artifacts is Cloudflare's Git-compatible, versioned storage. Each repo is an isolated Git smart-HTTP server with its own remote URL, refs, tokens and lifecycle. It is in open beta, so check the changelog under Sources before relying on a limit or price.
git and need a remote you can create,
fork and scope from code.Do not use it as a shared branch that many autonomous agents push to. Concurrent pushes to one ref are rejected as non-fast-forward and agents fall into retry loops. Give each agent its own repo (usually a fork) and merge deliberately.
| Interface | Auth | Use for |
|---|---|---|
Workers binding env.ARTIFACTS | The binding itself, no token | Create, import, fork, delete, read objects and files, mint and revoke tokens |
REST API under /accounts/$ACCOUNT_ID/artifacts | Cloudflare API token, Artifacts Read or Artifacts Edit | Same control plane from outside Workers |
| Git over HTTPS | Repo-scoped token, read or write | Clone, fetch, pull, push: the only way to write commits |
The binding and REST API are control plane only. There is no diff, merge or
commit-write API, and Workers cannot run the git binary. Writes happen in a
Git client: a CLI or server with git, a Sandbox or Container, or
isomorphic-git with an in-memory filesystem inside a Worker. See
references/patterns.md.
eu, us, or unrestricted when omitted) is set once at creation through
POST /accounts/$ACCOUNT_ID/artifacts/namespaces and can never change.
Create a jurisdiction-bound namespace explicitly before its first repo;
creating a repo under an unknown namespace name creates the namespace
implicitly, and that path cannot set a jurisdiction. Get approval before creating one; a
mistake cannot be undone.remote. The format is
https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/<namespace>/<repo>.git,
but read it from the create, fork, import, get or info() response rather
than building it.git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$REMOTE".
-c shows the token to other local users in ps; on shared hosts and from
programs, pass the same setting through GIT_CONFIG_COUNT,
GIT_CONFIG_KEY_0 and GIT_CONFIG_VALUE_0 instead (the git and tokens
reference shows both). Never git remote add a URL that contains a token;
it lands in .git/config.art_v2_<letter>_<40 hex>?expires=<unix seconds> (the docs still show
art_v1_<40 hex>; match both). read allows clone, fetch and
pull. write adds push. Scope defaults to write and TTL to 86,400 s
(min 60, max 31,536,000), so always pass both. One token covers one repo.
Never hand every agent one long-lived write token.repo.revokeToken(plaintext) if you will mint scoped tokens instead.receive-pack only. Clone and fetch work over v1
and v2, and shallow and deepen fetches work. Partial clone (--filter) and
include-tag are not supported. Stock git push works as is.env.ARTIFACTS.get(name) returns an RPC stub,
not metadata. Declare it with using, and call await repo.info() for
remote, defaultBranch and the rest.get() throws CREATE_IN_PROGRESS,
IMPORT_IN_PROGRESS or FORK_IN_PROGRESS, and REST returns 409 while an
import or fork runs. Retry with backoff on those codes only.ref,
before and after, not from message text.// examples/cloudflare-artifacts/src/session.ts
// wrangler.jsonc: "artifacts": [{ "binding": "ARTIFACTS", "namespace": "agents" }]
// Authorize the caller before this runs; it hands out a write token.
async function startSession(env: Env, agentId: string, sessionId: string) {
using base = await env.ARTIFACTS.get("baseline"); // a reviewed, trusted repo
// A repeat of the same session throws ALREADY_EXISTS here, before the cleanup below.
const fork = await base.fork(await repoName(agentId, sessionId), { defaultBranchOnly: true });
try {
using repo = await getWhenReady(env, fork.name); // the fork may still be copying
await repo.revokeToken(fork.token); // drop the initial 24 hour token
const token = await repo.createToken("write", 3600);
return { remote: fork.remote, branch: fork.defaultBranch, token: token.plaintext, expiresAt: token.expiresAt };
} catch (err) {
// Deleting the fork also deletes its initial write token.
await env.ARTIFACTS.delete(fork.name).catch((e) => console.error("orphaned fork", fork.name, e));
throw err;
}
}
// Hash the pair so ("a-b", "c") and ("a", "b-c") cannot collide.
async function repoName(agentId: string, sessionId: string): Promise<string> {
const data = new TextEncoder().encode(JSON.stringify([agentId, sessionId]));
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", data));
return "session-" + Array.from(hash.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
}
// Retry get() with backoff while a create, import, or fork is still running.
async function getWhenReady(env: Env, name: string, tries = 5): Promise<ArtifactsRepo> {
for (let i = 0; ; i++) {
try {
return await env.ARTIFACTS.get(name);
} catch (err) {
const code = (err as Partial<ArtifactsError>).code;
if (!code?.endsWith("_IN_PROGRESS") || i + 1 >= tries) throw err;
await scheduler.wait(250 * 2 ** i);
}
}
}The agent clones, commits its work, pushes, and confirms the remote branch
moved to its commit. $BRANCH is the branch returned above.
auth="http.extraHeader=Authorization: Bearer $ARTIFACTS_TOKEN"
git -c "$auth" clone "$REMOTE" work
# ...edit and commit in work/...
git -C work -c "$auth" push origin "HEAD:$BRANCH"
test "$(git -C work -c "$auth" ls-remote origin "refs/heads/$BRANCH" | cut -f1)" = "$(git -C work rev-parse HEAD)"Read only the file the task needs.
The versions this skill was checked against are listed in references/limits-pricing-errors.md. When the project's versions differ, trust its generated types and the live docs over this skill.
index.md.