Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,137 @@

All notable changes to the Toolpath workspace are documented here.

## Share and resume anywhere: S3, a folder, or Pathbase — 2026-08-10

**`path-cli`** (0.17.0) makes the share destination configurable. Bare
`path share` still goes to Pathbase by default, but it can now be
pointed at an S3 bucket or a plain folder once, and stay there — and
what you share to is somewhere you can browse and resume out of, not a
write-only hole.

**Designating a target.** `path target <value>` sets it, and
`path target` with no argument prints what's in effect and why:

```
path target ~/Dropbox/toolpath-traces # a folder — no credentials needed
path target s3://my-bucket/traces
path target pathbase # switch back
path target --clear
```

Setting a target **verifies** it, by doing what a share does: writing a
small object and removing it. That's the whole point of checking at
configuration time — a target is set once and used many times, so a
wrong one that survives costs a session pick and a derivation later, at
the moment the user wanted a result rather than a setup step. A listing
would only tell you about `s3:ListBucket`, which isn't the permission a
share needs; only a write answers the question actually being asked.
Folders go through the same path, which also creates them. `--no-verify`
stores a target unchecked, for a bucket that doesn't exist yet or a
laptop that's offline.

It's a top-level verb rather than a flag on `share` or a subcommand of
`auth`, because it's a persistent setting and it is not authentication.
The value is stored as `default_target` in `~/.toolpath/config.json` —
one place, so "where does my next share go?" has one answer.

A scheme-less value is a **local path**; spell a bucket `s3://…`. A
*bare relative* value (`my-bucket/traces`) is rejected outright rather
than quietly creating `./my-bucket/traces` and reporting success —
`./my-bucket/traces` says you meant it. Folder targets are stored as
`file://` URLs, so a stored default can't drift with the working
directory, but they're displayed and printed as plain paths.
Resolution: `--to`, then `$TOOLPATH_SHARE_TARGET`, then the stored
default, then Pathbase.

Nothing is inferred from which credentials happen to exist: a share
that silently changes destination is a data-egress bug, not a
convenience. The one guard is at the bottom of the order — if S3
credentials are stored, no Pathbase session exists, and no target is
set, `path share` refuses rather than falling through to the
*anonymous public* Pathbase endpoint.

**Legible object names.** A shared document lands at
`<date>-<topic>-<cache-id>.json` — e.g.
`2026-08-07-add-s3-support-to-share-claude-6f2a1c9e.json`. Every
component is a pure function of the document, so re-sharing a session
that has grown overwrites its own object instead of leaving a trail of
near-duplicates; the date comes from the session's *first* step, so it
doesn't move as the conversation continues. The point is that a
destination is a folder someone will open or a bucket someone will page
through, and it also makes listing cheap enough to build a picker on.

**Resuming from a destination.** `path resume <destination>` — a
bucket, a prefix, or a folder — lists what's there and offers a picker,
the counterpart to `path share`'s picker across every harness. Rows are
built from object names alone, so browsing a hundred shared sessions
costs one list request and zero downloads. A destination holding a
single document skips the picker. Anything ending in `.json` is still
treated as a document and fetched directly.

**Per-call override.** `path share --to <target>` takes the same forms.
The Pathbase-only flags (`--anon`, `--repo`, `--public`, `--url`,
`--name`) select Pathbase on their own, overriding an object target;
combining one with an explicit object `--to` is an error rather than a
silent resolution. The target is resolved before the harness scan and
the picker, and an `s3://` target is probed for reachability before the
picker too — a weaker check than `path target`'s, since the upload is
about to happen and will report its own failure; all it needs to buy is
not wasting a derivation on a typo'd bucket. Both checks bound their own
runtime (short timeouts *and* a retry cap, since the default ten retries
would otherwise multiply the timeout).

**Resume by location.** `path resume s3://bucket/key.json` caches
downloads under an `s3-<bucket>-<key>` id, so `--force` and
`--no-cache` behave exactly as they do for Pathbase. A document shared
to a folder is resumed with its plain path, which already worked.

**S3 credentials come from wherever you already keep them.** If you use
the AWS CLI, `path` needs no configuration at all: `~/.aws/credentials`
and `~/.aws/config` are read directly, `AWS_PROFILE` and `--profile`
select a profile, and the profile's region is used if you haven't set
one. SSO, `role_arn` chains, and `credential_process` profiles are
resolved by shelling out to `aws configure export-credentials` — the
AWS CLI's own resolver, so refresh and every future profile type stay
its problem rather than ours.

`object_store` alone would have covered only the server cases (EC2, ECS,
EKS); it reads no `~/.aws` because it avoids the AWS SDK. Taking on
`aws-config` to fix that would have meant 31 crates and an MSRV
treadmill — its family requires rustc 1.94.1 against a repo pinned to
1.94.0.

`path auth s3 status` now reports *which* credential source won, because
that's the first question when an upload fails.

**Storing keys is now the fallback, not the happy path.** `path auth s3
login` is for endpoints AWS tooling doesn't know about — MinIO, R2, Ceph
— where a scoped long-lived token is the right answer. It stores region,
endpoint,
addressing style, an optional `profile` name, and credentials at
`~/.toolpath/s3.json` (0600) — connection only, deliberately not a destination, so one stored
credential serves any number of buckets. It merges rather than
replaces, so `path auth s3 login --region eu-west-1` is a valid tweak;
run it bare in a terminal and it prompts, without echoing the secret.
`path auth s3 status` prints the settings in effect with secrets
redacted and environment-supplied values marked `(env)`. Stored values
win over the environment (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`,
`AWS_SESSION_TOKEN`, `AWS_REGION`, `AWS_ENDPOINT_URL_S3`); with
neither, the AWS credential chain still applies, so an EC2 instance
role needs no configuration at all.

**Plumbing.** `path p export object --input <ref> [--to DEST]` and
`path p import object <uri>`, both aliased `s3`. With no `--to`,
export writes wherever `path share` would.

Transport is the `object_store` crate, so one code path covers AWS S3,
any S3-compatible endpoint (R2, MinIO, Ceph, B2 — point `--endpoint` at
it), and `file://` for a folder. `memory://` is rejected: it's a fresh
per-process store, so anything "shared" there is gone before the
command exits. The folder backend is what the tests round-trip against,
so share and resume are exercised end-to-end without a network or a
mock HTTP server.

## Projected Claude sessions are resumable again — 2026-07-30

Two fixes found by live-resuming a projected session against the real
Expand Down
Loading