Skip to content

[release] v0.108.1 - #5698

Merged
mmabrouk merged 46 commits into
mainfrom
release/v0.108.1
Aug 4, 2026
Merged

[release] v0.108.1#5698
mmabrouk merged 46 commits into
mainfrom
release/v0.108.1

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

New version v0.108.1 in

  • web
    • web/oss
    • web/ee
  • services
  • api
  • sdks
    • sdks/python
  • clients
    • clients/python
    • clients/typescript
  • kubernetes
    • kubernetes/helm

mmabrouk and others added 13 commits August 2, 2026 20:16
Recut of #5277 onto current main. Behind AGENTA_DAYTONA_OPAQUE_SECRETS=process_local,
the runner creates per-sandbox Daytona Secret records and sends only Secret
placeholders in the sandbox create request instead of plaintext credential env vars.
The SDK resolves managed model and HTTP MCP credentials into typed wire descriptors
and fails closed when resolution is incomplete. Flag off, behavior matches main.

Re-expressed over main's later reworks: typed run failures, model capability
catalog, current-turn/attachment delivery, Pi built-ins rework, session-storage
rework, mount-credential tracking, @daytona/sdk 0.198.
- SDK: build_resolved_connection wraps classify_environment failures in
  InvalidConnectionConfigurationError (422), matching the effective_endpoint
  handling; malformed bindings no longer surface as 500s. Test added.
- runner: buildDaytonaSecretPlan rejects an opaque model credential whose
  binding name (case-insensitive) collides with a direct environment binding,
  so one name can never ride both envVars and secrets in a create request.
  local_use bindings now settle before opaque ones so wire order cannot evade
  the check. Tests added.
- runner: extracted one shared isDaytonaNotFound helper (daytona-secrets.ts)
  recognizing both the typed DaytonaNotFoundError and 404-shaped errors; used
  by Secret cleanup and the process-local sandbox lifecycle wrapper. Test added.
- hermetic-env AGENTA_DAYTONA_OPAQUE_SECRETS scrub: already present at HEAD
  (SCRUBBED list + per-test re-scrub); no change needed.
- daytona-secret-provider proxy staleness claim: rebutted, intentionally
  unchanged (all lifecycle methods are declared on the facade; attachments
  only affect create, which always rebuilds the delegate).
- configFingerprint environment-values claim: rebutted, intentionally
  unchanged (modelConnection.environment carries non-secret config only by
  contract; secret material rides typed credentials whose values are already
  stripped from the fingerprint).
Re-express the typed-wire delta on top of the Codex harness feature:
- keep wire_harness_mode / harnessMode alongside modelConnection; drop the
  retired flat fields main's copies still carried (provider, secrets,
  credentialMode) from protocol.ts, wire_models.py, and the fingerprint
- port main's new Codex tests (run-plan gates, wire golden) to the typed
  modelConnection shape and regenerate run_request.codex.json
- runner: tsc clean, 99 files / 1534 unit tests green; SDK: 1920 passed
Four problems, all caught by suites the port never ran locally.

1. The author's connection choice stopped reaching the runner. The port dropped
   `wire_model_ref()` because it also carried the retired flat `provider` field,
   but that method was the only emitter of the top-level `connection` reference.
   The runner still gates Pi's OpenAI-compatible models.json path on
   `request.connection.mode === "agenta"` and names the provider from its slug
   (`pi-model-config.ts`), so a named custom connection silently fell back to the
   generic provider-override env. Restored as `wire_connection_ref()`, which emits
   only `{mode, slug}` and stays empty for the project default so a plain run's
   payload is unchanged.

2. `_fake_runner_backend.py` still passed `secrets=` to `request_to_wire`, which
   the typed wire no longer accepts. It now mirrors the production sandbox-agent
   backend: the parameter is accepted for interface parity and ignored, because
   credentials ride inside `config` as `modelConnection`.

3. The services handler tests still built `ResolvedConnection(env=...)` and
   expected a resolution failure to degrade into an empty `runtime_provided`
   plan. Ported to typed credentials, and the degradation test became a
   fail-closed test. The shared no-credential stub now reads its provider from
   the harness capability table, because a fail-closed resolve must return a
   connection the post-resolve gate accepts for whichever harness is running.

4. The custom-connection replay test asserted the retired flat wire.

Also: the credential epoch now uses an HMAC keyed with a per-process random key
instead of a bare sha256 of the secret values. The epoch is only ever compared
within one process, so the key costs nothing, and the digest stops being
brute-forceable against candidate API keys. This is what CodeQL flagged.

Verified: runner tsc clean, 99 files / 1534 tests; SDK 2067 passed (unit +
integration) with the 10 pre-existing litellm xfails; services 100 passed.
Review found the feature had no documentation and, more importantly, no way
for an operator to turn it on: the environment variable was read straight from
process.env in the runner and was never plumbed through docker-compose or the
Helm chart, so setting it on the host did nothing.

- Renamed the flag to AGENTA_RUNNER_DAYTONA_OPAQUE_SECRETS. Every other runner
  Daytona setting is AGENTA_RUNNER_DAYTONA_*, and the bare AGENTA_DAYTONA_ prefix
  collided visually with the DAYTONA_* variables that configure the unrelated
  code-evaluator sandbox. The feature is unreleased, so no operator has set the
  old name.
- Plumbed it through all seven docker-compose runner services, the four env
  examples, the Helm deployment template, values.yaml, and values.schema.json.
- Documented it in the configuration reference (what it does, the two caveats:
  the Daytona API key needs Secrets permission, and AWS keys cannot be hidden)
  and in the how-agents-run concept page (why an agent reading its own
  environment is the threat, and what the sandbox sees with the flag on).

Also expanded the wire-contract comments the review asked about: what the
usage values mean and why there are exactly two, what endpoint covers beyond
OpenAI-compatible routes (Azure apiVersion, AWS/Vertex region), why a Daytona
secret candidate is called a candidate, why allowedHost can never be a
wildcard, and why the retired-field guard rejects rather than ignores while
`connection` is deliberately not among the retired fields.
CodeQL reports the alert on the createHmac call itself, and a suppression
comment only applies to alerts on its own line. The preceding-comment form did
not take, so the call is split out and carries the marker inline.
CodeQL flagged the credential epoch as an insecurely hashed password, and
GitHub code scanning does not honour inline suppression comments, so the check
stayed red. Rather than argue with the scanner, the design changed, and the
result is better than what it replaced.

The epoch answers one question: did the credential material change since this
session was parked? It never needed a digest to do that. It now holds the
material behind a `CredentialMaterial` value object and compares it with
`timingSafeEqual`.

Why holding the values is not a step backwards. A digest of an API key was
never much protection, because keys carry little enough entropy that a leaked
digest can be attacked offline, and the parked environment already holds the
plaintext anyway. The realistic risk is a value reaching a log line, and that
is now structurally impossible rather than a convention: the material sits in
a private field with no getter, and every route from an object to text
(`String`, template literals, `JSON.stringify`, `util.inspect`, so
`console.log`) returns a placeholder. This mirrors what the Python side
already does, where `ResolvedCredential` masks its value on dump and hides it
from `repr`.

A test pins every one of those rendering routes, so a future refactor that
drops an override fails loudly instead of leaking silently.

No behavior change: the same rotations evict and the same re-minted per-turn
bearers do not. Runner tsc clean, 99 files / 1535 tests.
The README still described `request.secrets`, which this change retired, and
said nothing about hiding keys from the sandbox. It now describes the
consumer-grouped shape, the reject-on-retired-field guard, and the Daytona
Secrets path with its three caveats.
…able mount

The durable session cwd is a geesefs mount over object storage, which has no
symlinks: `<cwd>/.codex/auth.json` flushes to the store as a 0-byte object and
comes back as an ordinary empty file. `symlinkCodexSubscriptionAuthFile`
guarded with `existsSync`, read that degraded entry as "already linked", and
returned early, so every turn after the first authenticated from an empty token
file.

Extract the lstat/readlink/replace logic `linkAgentFiles` already used for the
same hazard into `ensureDurableSymlink`, use it for both links, and attach the
Codex link to the durable-mount lifecycle so a mid-session remount
re-materializes it. A subscription run that still fails auth now says the
mounted login is empty or unreadable instead of asking for a project key.

Closes #5692
…lease gate

The Codex subscription bug in #5692 shipped through a gate that could not see
it: the only multi-turn journey stayed on the warm daemon, and the cell labelled
"Codex subscription" actually ran the Pi harness with an OpenAI-compatible
subscription provider, so the code that broke had no coverage at all.

Adds warm / cold1 / cold2 journeys (the tier vocabulary from
reports/warm-approvals-qa.md), running in every cell, over a store-backed
durable cwd:

- cold1 forces an eviction from the client by changing the agent's instructions
  (a configFingerprint input), so the runner unmounts and remounts the cwd.
- cold2 replaces the runner replica through an operator hook (SIGKILL + owner
  TTL wait); local sandboxes must refuse, remote ones resume cold.
- The store is a precondition: the journeys resolve the session's durable mount
  over the API and SKIP (FAIL with --require-store) when there is none, so a
  green result can never mean "the store was not in play".
- Evidence is store-side, not prose: the client reads the agent's token straight
  out of the object store and writes a store-only file the agent must read back,
  plus the turn ledger's agent_session_id/sandbox_id and any 0-byte objects in
  the cwd.

Cells: S2 (codex + runtime_provided — the genuine subscription cell), X2 (codex
on daytona, so a completed cold 2 is observable), S1 relabelled as what it is.

Also folds in the QA-infrastructure fix left over from the v0.108.0 release run:
P2 needs connection mode `agenta` plus the full `<slug>/custom/<model>` key
under v0.107.x resolver semantics.

Refs #5692
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 4, 2026 10:18am

Request Review

@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Aug 3, 2026
…-flag

feat(frontend): voice input as an experimental per-user settings flag
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-04T10:18:41.326Z

…bare 403

A Daytona API key that can create sandboxes does not automatically have the
separate permission to manage Secrets. When it does not, enabling
AGENTA_RUNNER_DAYTONA_OPAQUE_SECRETS makes every run with a hideable
credential fail at sandbox creation, and the raw provider error is a bare
Forbidden that says nothing about the flag that caused it.

This is the first thing an operator hits when turning the feature on, so the
create path now recognizes a permission refusal and re-raises it as a message
naming the variable to fix, the permission to grant, and the way to revert.
The provider's own error is kept as the cause so the logs keep the detail.
Every other failure keeps its original error untouched.

The docs promote the same point from a bullet to a warning, and say plainly
that the runner does not fall back to plaintext, because doing the
unprotected thing silently would be worse than stopping.

Runner tsc clean, 99 files / 1538 tests.
…#5699)

* chore(ci): cap preview lifetime at 6h and sweep hourly (#5650)

* fix(ci): pass cleanup inputs through env and validate the age (#5650)
…he gap

Review catch. `wire_connection_ref()` emits a top-level `connection` field for
a self-managed or named Agenta connection, and the runner reads its slug to
register a custom OpenAI-compatible Pi run in Pi's models.json. But
`WireRunRequest` no longer declared the field, so the exported contract schema
omitted something the implementation both produces and requires. A client
generated from that schema would drop it, and those runs would silently fall
back to the generic provider-override path.

The field went missing because it sat next to the flat credential fields this
change retired and got swept out with them. It is not a credential. It is the
author's connection CHOICE, which is non-secret routing config.

Restored `WireConnection` and the `connection` field on `WireRunRequest`.

Two reasons nothing caught this, both now closed:

- `KNOWN_REQUEST_KEYS` was stale in exactly the same way, and its guard is a
  subset check over three sample payloads, none of which names a connection.
  Added the key, and added a test asserting the set EQUALS the schema's
  declared aliases in both directions. A key the schema declares but the
  producer never emits is dead contract surface, so equality is the honest
  assertion.
- A validation-based test could not have caught it either: `_WireModel` sets
  `extra="allow"`, so a payload carrying a field the schema forgot still
  validates cleanly with the field quietly demoted to an extra. The new test
  is structural rather than validation-based for that reason, and the second
  new test round-trips a named connection through `model_dump` to prove it
  survives as a modelled field rather than an extra.

I verified both guards fail when the schema field is removed, so neither is a
test that can never break.

Also corrected the runner's own doc comment on the field. It claimed "the
current SDK resolver does NOT send it", which described the regression rather
than the contract, and would have led the next reader to delete the
models.json path as dead code.

Verified: SDK 2069 passed with the 10 pre-existing litellm xfails, services
100 passed, runner tsc clean and 99 files / 1538 tests, ruff clean.
`RunPlan` had grown to thirty fields on one flat interface, and every
consumer took the whole thing regardless of how little it read. Raised in
review of #5670.

The fields are grouped into `credentials`, `workspace`, `tools`, and `prompt`.
The five identity fields (`harness`, `acpAgent`, `sandboxId`, `isPi`,
`isDaytona`) stay at the top level because almost every consumer branches on
them, and `sandboxPermission` stays there too since the declared security
boundary is its own concern.

The payoff is not the tidier interface. It is that a consumer can now say what
it actually touches. `prepareWorkspace` used to take a nine-key `Pick` of the
flat plan; it now takes three named slices and the parameter type reads as a
description of the function. Several other consumers narrowed the same way,
which means a future change to, say, credential delivery has a compiler-checked
list of what depends on it.

The one rename is `plan.prompt` to `plan.prompt.text`, forced by the group
taking the name. Nothing else was renamed, no logic changed, and no `any` cast
or `@ts-expect-error` was added.

Runner tsc clean, 99 files / 1535 tests, the same counts as before the change.
[refactor] Group RunPlan by concern instead of one flat bag
[feat] Deliver agent credentials through Daytona Secrets
@dosubot dosubot Bot removed the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 3, 2026
The Daytona cells (C2, C4) already existed, so the matrix covered the sandbox.
What no journey covered was whether credential hiding actually works.

That gap matters more than it looks. A plaintext provider key works exactly as
well as a Daytona Secret placeholder does, so if hiding silently stopped
working tomorrow, every existing journey would still pass and the gate would
report green. The security property is invisible to a test that only asks
whether the product works. Something has to go into the sandbox and look.

The new `secret_opaque` journey runs bash inside the sandbox and reads the
first 11 characters of the provider key variable. It passes when that value
begins `dtn_secret_`.

Two deliberate details:

- It reads 11 characters, never the whole value. That is exactly enough to
  tell `dtn_secret_` from a real `sk-` key, and it never asks the model to
  print a credential. A safety-trained model may refuse to do that, which
  would turn a real check into one that fails for the wrong reason.
- It requires the bash call to have genuinely executed, so it cannot pass on
  an absence. A refused call, an empty variable, or a model that declined to
  answer all read as FAIL. An absence-only check ("no key in the reply") would
  have passed on every one of those while proving nothing.

Daytona only. It SKIPs on local cells, where the harness runs inside the
runner container and there is nothing to hide the key from.

Also noted in SKILL.md that C2 and C4 now need the runner's Daytona API key to
have permission to manage Secrets, since hiding is on by default as of the
change earlier in this release.

Committed straight to the release branch: this is release-gate tooling for a
feature already merged into it, and the gate is about to run.
Removed duplicate text and added a demo link image.
Update README with demo link and remove duplication
Every streaming agent turn ended with a `data-agent-error` and an `error` frame on
the browser's wire, even though the answer itself arrived intact. The text was right
and the finish reason was `stop`, so it looked like a working turn with an error
stapled to it.

`_stream_in_redaction_scope` wraps the event stream in `redaction_context`, whose
`finally` resets the ambient redactor's ContextVar token. PEP 567 gives generators no
context of their own, so the install lands in whichever caller's context ran the first
`__anext__`, while the close runs wherever the stream is finally exhausted. In the real
service those are different tasks, and `ContextVar.reset` rejects a token from another
context. The resulting ValueError escaped into the stream adapter, which turned it into
an error frame, and it took OpenTelemetry's own context detach down with it.

`reset_active_redactor` now falls back to installing a fresh empty redactor when the
token is foreign. That keeps the property the reset exists for: the finished run's
deny-set stops being reachable, so a later run sharing the context can neither see nor
accumulate it.

The regression test closes a stream from a different task than the one that opened it
and fails with the exact production ValueError without this change.
The credential-hiding journey asked the sandbox to print the first eleven characters
of the provider variable and looked for the `dtn_secret_` placeholder. That had two
problems.

It could write key material into the results file. Eleven characters are harmless while
hiding works, but on the exact failure this journey exists to catch, the reply would
carry a slice of a real provider key straight into the transcript.

It could not run on the codex harness. The check required seeing a bash tool call
complete on the wire, and codex runs shell through native exec frames that the tool-call
probe cannot read. Cell X2, codex on Daytona, therefore failed for a reason that had
nothing to do with credential hiding.

The sandbox now classifies its own variable with a POSIX `case` and echoes a verdict word
carrying a nonce this run invented. Only the verdict crosses back, so no key material can
reach the transcript, and the nonce keeps the journey unable to pass on an absence: a
refused call, an unset variable or a model that declined all still read as FAIL. Dropping
the tool-call requirement is what makes it work on every harness.

Verified on staging: C2, C4 and X2 all pass.
The cold-2 journey demanded that a local-sandbox resume REFUSE with "is not the owner
of session". It cannot, and the journey's own transition is why.

`isLocalRunnerEligible` allows a run when the session owner is unknown or is this
replica. The transition deliberately waits out the session-owner key, so that a resume
does not fail on a stale owner from the replica it just killed. Once that key lapses,
the ownership claim hands the session to whoever asks next, which is the replacement
replica. The guard then sees itself as the owner and correctly does not refuse. Waiting
for the refusal to become impossible and then asserting it is a test that can only fail.

Refusing is right when the original owner is still ALIVE, and this journey never creates
that: it needs two replicas at once, and the gate drives one deployment over HTTP. The
runner's own session-ownership tests cover the pure function; a two-replica end-to-end
journey is a follow-up.

What is actually correct after a genuine replica loss is what the remote tiers already
assert. The dead replica took its local sandbox with it, so the replacement has to
rebuild the conversation from the durable working directory in the object store. Local
cold 2 now asserts that same resume, and still fails loudly if a refusal appears, since
that would mean the owner key outlived the wait and the run measured the wait rather
than the product.

Verified against a local stack on this release: a SIGKILLed replica resumes cold and the
durable cwd token comes back.
…y rotation

Two paths this release depends on had no coverage at all.

The first is the resume users actually hit. The session pool parks a Daytona sandbox
after two minutes idle, which STOPS it rather than deleting it, and the next turn
reconnects to that same sandbox. The existing cold-1 journey forces a config change
instead, which deletes the sandbox and builds a fresh one with freshly created Secrets,
so it never touches the reconnect path. That matters now that credentials arrive as
Daytona Secrets: a Secret that quietly stopped substituting after a stop and start would
leave the entire matrix green. The new `park` tier changes nothing and simply waits, then
proves the resume was a reconnect rather than a recreate by reading the turn ledger, where
a reconnect shows exactly one sandbox id across the turns and more than one harness
session.

The second is rotating a key while a conversation is live. Credential values are
deliberately kept out of the session config fingerprint and live only in a separate
credential epoch, so that epoch check is the only thing standing between a rotated key and
a warm sandbox that keeps using the old one. Nothing else in the gate would notice if it
broke, because a stale key still answers. The new `rotate` journey replaces the vault key
with a decoy no provider accepts and requires the next turn to FAIL, since a success there
means the previous credential was still being served. It then restores the real key and
requires the conversation to come back with its durable working directory intact. The
vault is restored in a `finally`, because leaving a shared deployment holding a decoy
would break every cell that ran afterwards.

Verified on this release: `rotate` passes against a local stack, and the vault comes back
to its original value.
…ontinuity

The new park tier required the harness session id to CHANGE across a parked resume. It
does not change, and it must not: preserving it across a rebuilt session is the entire job
of the runner's session-continuity store. So the check asserted the opposite of the
intended behaviour and failed all three Daytona cells on a product that was working.

The real discriminator is the sandbox id. Reconnecting to a parked sandbox keeps it, while
a rebuild creates a new one, and cold1 on the same deployment reports two sandbox ids for
exactly that reason. That contrast is what makes one id here evidence rather than an
assumption.

The remaining ambiguity is honest and now written down: from the client alone, "parked and
reconnected" and "still pooled and served warm" both keep the sandbox. The transition
settles it by construction, since it idles well past the pool TTL, and the runner log line
in the evidence remains the definitive witness.

Verified on staging: C2, C4 and X2 all pass, each reconnecting to the same sandbox with
the durable working directory and the store-only file intact.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ mmabrouk
❌ github-actions[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

v0.108.1 validates a credentialed connection's endpoint far more strictly on a remote
sandbox than it used to. The host has to be plain HTTPS on the default port with a real
fully-qualified name, because that host is exactly what the credential's Daytona Secret is
pinned to. A self-hosted proxy on a non-default port, which is an ordinary setup, is now
refused outright where it worked in v0.108.0.

Nothing in the gate could see that. The existing custom-provider cell runs only on the
local sandbox, and a local sandbox never builds a secret plan, so it never reaches the
validator. P3 is that same cell on Daytona, which is the surface where the rejection
happens and where a self-hoster would hit it.

Verified on staging: chat, tool and secret_opaque all pass, so the custom provider works
end to end on a remote sandbox with credential hiding on.
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 4, 2026
Two releases shipped without an announcement, and seven documentation pages had drifted
into saying things those releases made untrue.

**The announcements.** "Run Your Agents on Codex" covers the third harness, its five
OpenAI models, both sandbox types, tool approvals, MCP servers, image attachments, and the
two ways to pay for the model. It opens with a demo video. "API Keys Are Hidden from Agent
Sandboxes" covers credential hiding on Daytona: what it does, what it covers, what it
deliberately does not cover, and a warning block telling self-hosters to grant their
Daytona key permission to manage Secrets before they upgrade, since there is no fallback
to plaintext by design.

**The documentation corrections.** Two pages still said Agenta ships two harnesses. One
said MCP servers were Claude Code only. One still answered "Yes" to whether the model key
crosses into a Daytona sandbox, which is exactly what this release stopped. The
configuration reference contradicted itself about the default. The Daytona page never
mentioned the Secrets permission its own setup now requires. Publishing a changelog that
its own documentation disagrees with is worse than publishing nothing, so all of it is
fixed here.

**The roadmap.** Durable sessions, the agent work folder, and batch tool approvals moved
to shipped. Channels and mobile moved from planned to in progress. The two new features
were added.

Also fixes a runner error message that told users to pick "a non-Pi harness (e.g. claude)"
for an MCP server. Codex accepts them too, so it now names both and stops sending Codex
users looking for a harness they were already allowed to use.
The v0.107.0 entry was titled "File Attachments in Agent Chat" and described the mechanics:
you can paste, drag, or upload, and the files belong to the session. That is accurate and
it undersells the feature badly. The point is not that a chat box accepts an upload. The
point is that you, your team, and your agents now share one folder in the cloud, instead of
the material living on one person's machine where nobody else can reach it.

Retitled to "Shared Workspace Files" and rewritten around that. The summary leads with the
shared folder, then the two scopes and why the difference matters: a conversation's files
are the desk for today's work, while each agent's own folder persists across every
conversation, so pointing the instructions at it means the agent starts each session
already knowing your material.

Adds a demo video, and drops the two screenshot placeholders it replaces.

The slug stays `file-attachments-in-agent-chat`. It is already live and the site has no
redirect plugin, so changing it would break a published URL for a title fix.

The matching roadmap entry is retitled and rewritten to agree with it.
Both videos in this entry sat in bare iframes. An iframe is inline by default, so it
lands on the text baseline with no vertical margin and picks up a descender gap, which
is why the video looked jammed against the paragraph above and below it.

Every other changelog entry wraps its iframe in a centred flex block with 20px of margin
top and bottom. These two now do the same, so the whole changelog spaces its videos
identically. No other entry had the problem.
…ntial-hiding

[docs] Announce Codex and Daytona credential hiding
@mmabrouk
mmabrouk merged commit c1063fb into main Aug 4, 2026
32 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants