The selector grammar ran against a heads/-prefixed string, so its
leading-character rules saw the prefix instead of the branch: names git
itself rejects, like -foo or HEAD, passed admission and only failed
later at sandbox clone time. Check the bare branch name and reject a
literal HEAD explicitly.
Also build the Git projection's origin URL through
GitHubRepositorySlug::https_url so the URL grammar keeps one owner.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lowering and compiler rejections now carry the top-level error message
in the 422 detail, matching the diagnostic depth the legacy manifest
lane already returns for identical defects; the full source chain stays
in the server log.
Pre-persistence store failures stop claiming run_persistence_failed:
credential-store reads return credential_store_error and run-variable
snapshots return variable_store_error, so alerting keyed on codes
triages the failing subsystem instead of a persistence outage.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both lanes now deserialize the raw request bytes directly instead of
round-tripping through a serde_json::Value, which silently collapsed
duplicate JSON keys to last-key-wins on the legacy manifest lane and
stripped line/column locations from manifest parse errors.
When neither lane accepts the body, attribution now recognizes a
defective manifest by its required keys, so a legacy manifest carrying a
stray workflow_version_id keeps its 400 manifest error instead of being
misrouted to a 422 run_intent_invalid describing a schema the caller
never used.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
create_run_from_manifest and create_run_from_intent were byte-identical
apart from the body type; fold the shared request/retry plumbing into a
private submit_create_run(CreateRunRequest) so the two public entry
points stay thin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The create-run dispatcher deep-cloned the parsed JSON body once to
attempt the RunIntent shape and again for the RunManifest fallback,
so every legacy manifest request paid two full copies of a body that
carries entire workflow bundles. Deserialize both shapes from a
reference to the parsed value instead; routing and error attribution
are unchanged.
Also bind the lowered goal slot once in inline_goal_file rather than
re-navigating the settings layer and asserting the goal is still there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The intent and legacy-manifest create handlers each carried a full copy
of the same post-admission sequence: LLM readiness resolution, graph
compilation and model pinning, persistence, summary read, managed-run
registration, title-generation spawn, and the 201 response. The copies
had already drifted on when the run ID is resolved (before compilation
in one lane, after in the other).
Extract one finalize_created_run tail, with a small CreatedRunErrorStyle
carrying each lane's pinned error mapping and log lines so the wire
contracts are unchanged. Both lanes now resolve identity before
compilation and share the parent-link validation, which lets the
PinnedRun copy of PreparedRun's identity accessors be deleted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Git-target grammar (slug, branch, and SHA rules plus the derived
origin URL) was implemented twice with no shared code path: once in
server admission and again in sandbox start, so the two could drift and
disagree about which persisted targets are valid.
Own it once as RunTarget::validate() in fabro-types, next to the
primitives it uses, returning the canonical target together with its
derived GitContext projection. Admission consumes it directly, and the
start path re-derives the expected clone source from the same rules
before checking the persisted projection against it. The start path now
also moves the derived strings into the sandbox spec instead of cloning
them.
While reordering admission around the shared validator, run the pure,
in-memory checks (target grammar, environment id) before the blob-store
closure fetch and lowering so malformed requests no longer pay for
version-store I/O.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Preserve the SQLite auth-session release notes alongside main's July 26 fixes and retain all current changelog navigation entries. Make the refresh-token rotation timestamp assertion deterministic after the merged suite exposed its wall-clock race.
Apply the cleanup findings from a four-angle review (reuse,
simplification, efficiency, altitude) of the demotion change:
- Share one size gate: serialized_if_over now backs both offload_value
and demote_value_for_prompt, restoring the cheap short-string and
scalar pre-checks so per-node demotion no longer serializes every
small value just to measure it.
- Stop re-writing blobs every node: materialize_value_bytes writes the
sandbox file directly from the in-hand bytes and short-circuits on the
content-addressed file's existence, so an already-demoted value costs
one existence probe instead of a store round-trip per node visit. The
local file write is shared with materialize_blob_ref.
- Demote over the resolved snapshot map instead of re-snapshotting a
Context copy, making the context and outcome loops symmetric and
saving a full deep clone per node; the fidelity lifecycle builds the
Context after the pass.
- Skip the pass entirely for Full and Truncate fidelities (nothing
renders context values), except parallel nodes whose branch stash may
render at a richer fidelity.
- Build is_preamble_hidden_key on is_engine_internal_key instead of
restating its prefixes, and call it directly from the preamble
renderer rather than through a wrapper.
- Document that outcome updates are demoted wholesale and that
BranchWorkItem.item carries the prompt-ready (possibly demoted) item;
drop the item rebinding and redundant test assertions; restore the
local integration test's confinement assertion and make the remote
one non-vacuous.
Skipped by choice: unifying the crate's several truncation helpers and
rendering the marker through the "See:" pointer family (cross-module
coupling out of proportion to the preview cosmetics), per-branch
demotion inside parallel.results (wholesale demotion is what bounds the
total), and cross-node demotion memoization (the file-existence
short-circuit already reduces repeats to a stat).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FH8Jj9Y4E4Tu5g1jwDtHAb
Compact and summary preambles render workflow context values and stage
outputs with no per-value size limit. A late-run node inherits everything
the run has accumulated, and one oversized value (a join result, a jobs
list, a single-line command emit) can push the composed prompt past the
model's context window. A security-review run failed exactly this way:
its dedupe stage assembled a ~1.8M-token prompt against a 1M-token model
limit, made almost entirely of accumulated context the agent never
needed inline.
Reuse the existing blob machinery at the last mile. Before the preamble
builders run, any resolved context or outcome value whose serialized
JSON exceeds 8KB is persisted as a content-addressed blob, materialized
as a real file in the sandbox, and replaced with a small marker holding
a preview, the byte count, and the file path. The agent reads the file
if it needs the data. for_each items get the same treatment at fan-out
with a more generous 64KB budget, since the item is the branch's work
assignment; branch labels still come from the full item. Keys the
preamble never renders are left alone, and a value that fails to demote
stays inline and is logged: demotion bounds prompt size, it does not
gate execution.
The two downstream-resolution integration tests asserted that resolving
text values writes no files; demotion now legitimately materializes the
oversized response for preamble use, so they instead pin that resolution
returned the full inline text and that nothing is written outside the
sandbox blob directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FH8Jj9Y4E4Tu5g1jwDtHAb
- Make the failure watch channel the single record of the latched
failure; drop the worker task's mirrored local state.
- Replace the hand-rolled wait loop with watch::Receiver::wait_for.
- Extract race_persistence/flush_or_stop helpers so the select!/flush
scaffolding in RunSession::run exists once instead of three times.
- Return RunEventPersistenceError from append_event_to_sink and add a
From impl on Error, replacing four hand-written per-event message
strings with the event name derived from the event itself.
- Dedupe the RunCreated test seed literal in initialize.rs and drop the
dead BlockingHandler::simulate override.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ryyhtbc1eNtCLw8GjrFQXZ
Make RunCloneSettings::DEFAULT_DEPTH the single owner of the default
depth, and interpret the "0 = full history" sentinel in one place via
RunCloneSettings::depth_limit(). Docker's clone_depth becomes
Option<usize> to match Daytona's encoding, with a shared
depth_argument() helper for both git command builders. Drop the
unreachable Option on the resolved depth field, the hand-written
DaytonaSettings::Default, and the pure-forwarding
daytona_git_clone_options helper. The blob-import test helper reuses
the pool's own connect options instead of rebuilding a partial copy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bgXj5J218RXfiT72qhbLV
Consolidate the copies that review found across the feature:
- One GITHUB_CREDENTIAL_HELPER / GITHUB_CREDENTIAL_HELPER_KEY pair in
fabro-github, with apply_probe_git_env() for probe commands; the runtime
git bridge, server preflight probe, and live contract test all consume it
so the probes exercise exactly what the bridge configures.
- GitHubRepositoryAccess::resolve_verified_token() owns the
resolve-installations-then-mint choreography shared by server preflight,
workflow initialization, and the live test.
- A shared lookup_installation() helper backs both the shared-installation
resolution and the mint's installation lookup.
- The contents = read|write rule lives once as
RunIntegrationsGithubSettings::contents_permission_allows_repository_access.
- The preflight probe paces retries with fabro-sandbox's exported
replication_backoff() (3s/9s) instead of a contradicting 1s/2s loop, and
shares one run_ls_remote() runner with the existing remote-ref check.
Also: collapse the dead Ok(None) arm and repeated error blocks in the
preflight token check, drop the derivable bridge_entry_count(), privatize
resolve_permissions() behind resolve_integration(), make
GitHubRepositorySlug ordering/hashing allocation-free, and use EnvVars
constants for env names.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brave stays the default. Shops that already vault VENICE_API_KEY
can drop BRAVE_SEARCH_API_KEY by setting
[server.integrations.search] provider = "venice".
Co-authored-by: Cursor <cursoragent@cursor.com>