Local recording and saved evidence uploads
loom run records fresh executions in state.jsonl and events.jsonl under
.loom/.runtime/runs/<run-id>/. These JSONL recordings are local-only.
Neither web.upload: true nor LOOM_WEB_UPLOAD=true enables their delivery.
An enabled upload session reports the local-only limitation without changing the
local result or exit code. Inspect the exact returned ID with loom logs <run-id>
or the MCP run and event readers.
Explicit upload preparation and retry support retained historical evidence from earlier producers. They do not convert a fresh JSONL run into a native capture. The following destination, credential and transfer rules apply to those saved formats.
Use loom web setup project to save the repository's complete, non-secret web tuple and local enrollment. The web.upload setting and LOOM_WEB_UPLOAD describe automatic-upload consent for producers that support it; they do not add upload support to current JSONL runs. Any explicitly supplied LOOM_WEB_UPLOAD value other than true disables automatic upload, including uploader credential lookup and network access. Authentication alone does not enable uploads.
With upload enabled, any of LOOM_WEB_URL, LOOM_WEB_PROJECT_ID, or LOOM_WEB_TOKEN_ENV selects the legacy CI mode. It requires LOOM_WEB_UPLOAD=true and a complete validated URL, project UUID, token variable name, and token value. A partial CI tuple fails; no field comes from repository configuration or the local credential store. With no legacy selectors, the repository's complete version-1 web tuple must match a local enrollment for the canonical repository root, origin, project, and upload choice. A copied repository file alone cannot authorize credentials.
Saved-evidence delivery binds its destination, enrollment and selected credential before transmission. A still-valid selected project token is used without refreshing an account grant. An expired CLI-issued token can be renewed only by naming and proving possession of that predecessor; the Web server must confirm expiry and lineage. Manual, revoked, unsafe, missing-selected, or ambiguous credentials require explicit reconnection. A lost issuance or renewal response leaves a durable recovery marker; the CLI does not issue again with a new operation ID. Explicit retry requires an already selected eligible project credential and cannot make an initial grant.
Only a project token reaches ingestion. A 401 or 403 stops the upload without retrying under another credential and marks the selected credential as rejection-unknown. A later run may attempt only the named, server-verified expiry renewal when eligible; otherwise run loom auth project to reconnect. The CLI never opens a browser or device flow during upload.
Upload warnings identify a bounded stage and reason with a next step. Configuration and enrollment failures call for checking the repository destination; private-store failures call for inspecting storage; interrupted issuance or renewal calls for reviewing loom auth project status and Web credential history before another approval. Transport or timeout failures do not imply the selected credential was rejected. Server response text and credential material are never included in these warnings. The status command distinguishes unresolved historical operations from operations that block the currently selected credential.
Current local recording masks values resolved from workflow secret declarations before persistence. With an upload session enabled, values already collected while binding that session also protect JSONL recording and console output before preparation starts: the selected project token, relevant already loaded account values, and values explicitly named by LOOM_WEB_REDACT_ENV. This protection does not enable delivery. It persists through cleanup and upload configuration failures, performs no additional credential or environment discovery, and does not inject those values into jobs. Disabled upload performs no uploader credential lookup. Saved-evidence upload audits frozen originals and rejects delivery when a known value remains; it never rewrites originals or retry bytes. Unknown, job-generated, or transformed secrets remain outside exact-match protection. The private account store remains accessible to other trusted workflows running as the same operating-system user; it is not a process sandbox. Account store handles are not passed to jobs or recording.
Retained historical native captures
Earlier producers may have retained native hierarchies under
.loom/.runtime/runs/<run-directory>/. Their run descriptor, immutable history,
checkpoint inventories, original event batches, protected output segments and
final seal supply native authority. The uploader verifies identities, references,
hashes and closure using the same reader as local inspection. It does not remap
that historical evidence into another receipt format. Current two-file JSONL
directories do not supply this native authority.
Authenticated capabilities must match loom.native-file-transfer.v1,
loom.native-run-artifacts.v1, the exact pinned schema digest and SHA-256, and
advertise required file kinds and file/run limits. Native transfer uses separate
/api/native/v1 routes. Historical receipt-only Web deployments are unsupported
for these runs; retained files stay pending without downconversion.
Exact file bytes are sent with their original paths, UUIDs, lengths and hashes.
Each accepted or duplicate acknowledgement must bind those same fields. The
private ledger under
.loom/.runtime/native-uploads/<run-directory>/<attempt-UUID>/ contains immutable
selection/destination bindings and per-file acknowledgements. It contains no
base64 evidence bundle and no credentials. Source files must remain available for
retry. A conflict or changed destination stops delivery.
Metadata, events and protected output are supported file kinds. Arbitrary artifact
bodies remain local by default. Explicit LOOM_WEB_NATIVE_ARTIFACT_BODIES=true
and the server's advertised body authorization are both required to send them.
Excluded bodies remain pending; metadata acknowledgements cannot prove a complete
remote inventory. Locally partial evidence requires the server's partial-evidence
capability and remains partial after transport.
Concurrent deliveries share bounded project requests, bytes and concurrency.
Transient failures use bounded backoff respecting Retry-After; exhausted budgets
retain exact originals and acknowledged progress. Success means native files
acknowledged, without an independently verified complete receiver state.
Historical v1/v2/v3 receipt upload bundles remain immutable and keep their
version-specific retry route.
Delivery timing
| Setting | Repository config | Environment | Flag | Default |
|---|---|---|---|---|
| Retry interval | web.uploadRetryInterval | LOOM_WEB_UPLOAD_RETRY_INTERVAL | --upload-retry-interval | 10s |
| Total budget | web.uploadTimeout | LOOM_WEB_UPLOAD_TIMEOUT | --upload-timeout | 120s |
Both loom run and loom web upload retry accept the flags. Each field resolves
independently: an explicitly supplied flag wins over a present environment
value, which wins over repository config, then the default. Empty or invalid
winning values fail without fallback. Use a positive integer followed by s,
m or h; the resulting duration must be between 1s and 1h inclusive, with
interval no greater than timeout. Version-1 configs may omit both fields.
Project setup preserves optional timing values.
Timing settings never enable fresh JSONL uploads. Explicit saved-evidence
delivery does not execute jobs or change their recorded result and exit code.
Credential refresh or eligible named renewal and delivery
share the total budget. A request gets at most five seconds, further bounded by
remaining time. Retryable failures repeat until the deadline, waiting
max(interval, valid Retry-After). A server's 60-second delay fits the default
budget when enough time remains. If the next wait leaves no usable request time,
the CLI stops incomplete. Cancellation and terminal authentication, conflict,
unsupported-version, bad-data or quota errors stop delivery promptly.
Prepare a finalized run offline
Select the native run directory from its original repository or a directory beneath it:
This command accepts retained historical native captures, not current JSONL run directories. It does not synthesize missing native authority or rerun jobs.
loom web upload prepare --run-id 20261005T120000Z-build-abcdefgh
Use the actual directory name printed by your run. Preparation runs zero jobs and
makes no network, environment or credential reads. It validates the native
originals and saves a private immutable selection binding in
.loom/.runtime/native-uploads/<run-directory>/<attempt-UUID>/selection.json.
No evidence bodies are copied. Preparation preserves the existing run/attempt
identities and exact bytes; it does not issue authority, change consent or claim
delivery.
The selector is one explicit safe run directory name. There is no latest-run scan, arbitrary path or workflow resolution. Unsafe, changing, conflicting or missing authority fails without a sibling-receipt fallback. Success reports the native run UUID, existing attempt UUID as the upload ID, file count and declared evidence state. Keep the original hierarchy for retry.
Retry one saved upload
Run from the original enrolled repository or a directory beneath it:
loom web upload retry \
--run-id 20261005T120000Z-build-abcdefgh \
--upload-id 22222222-2222-4222-8222-222222222222 \
--upload-retry-interval 10s --upload-timeout 3m
Both selectors are required. The native run directory name selects exactly one rooted hierarchy; the upload ID must equal its attempt UUID. Retry revalidates the originals, negotiates the pinned capability and sends only files without durable acknowledgements. It executes zero jobs, never remasks files or replaces identities, and retains progress after interruption. If an acknowledgement was lost, an exact duplicate transfer is safe. Invalid hashes, identity conflicts or changed bindings stop delivery.
Explicit retry authorizes only this invocation, even with web.upload: false or
LOOM_WEB_UPLOAD=false; it does not change future-run consent. The retained
origin/project must match the current authorized tuple. Stored mode requires the
exact repository enrollment and an already selected eligible project credential.
Retry cannot make an initial grant or fall back to broader credentials. CI mode
requires the complete existing destination/token tuple. Credential renewal and
transport share the delivery budget.
Historical saved uploads still use their original loom-run-local-N selector and
UUID. Receipt bundles under .loom/.runtime/logs/.../web-upload/ or
.loom/.runtime/canonical-uploads/ are strictly validated and replayed in their
original versions; they are never relabeled as native files. No historical bundle
is overwritten by native preparation or retry.