You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Repin Harbor to 504c2518 (#1076)
* fix(ci): staging workflow coordination and sync guard fixes (#1075)
* fix(ci): coordinate staging deploy with the database bootstrap
* fix(ci): sync guard runs on main pushes and gives safe recovery advice
* fix(ci): deploy staging after the bootstrap mirror
The shared staging-environment concurrency group serializes staging-deploy
with staging-db-bootstrap but does not order them. When the deploy wins the
lock on a bootstrap-triggering push, it migrates the pre-mirror branch and
nothing deploys after the mirror. The bootstrap now dispatches its own
Staging Deploy at the end, so the post-mirror deploy no longer depends on
which run started first. It is skipped after a password reset, where the
stale STAGING_DATABASE_URL secret would fail the deploy at migrations.
Pass --ref staging in the printed deploy recipe so the runbook does not
depend on the repository default branch staying staging.
State the supersede rule where it matters: only the staging tip is
guaranteed a deploy run, so the promotion preflight now explains that a
commit staging moved past may never have deployed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PtexagKz2Si6SVLZoFKrz
* fix(ci): publish the staging DB secret before the mirror
The password-reset recovery recipe reads the new URL from the
oddish-staging-db Modal secret, but that secret was published after the
multi-hour mirror. An operator who acted on the printed warning got the
pre-reset URL, and a failure between the reset and the publish left the new
password with no durable copy at all, because the Supabase API redacts it.
Publish the secret immediately after the branch step resolves the URL. Every
later step reads steps.branch.outputs.db_url directly, so none of them depend
on the old position.
Add a credential check to Staging Deploy. A deploy queued in the shared group
still runs after a password reset and still fails, which is correct, since
staging is not deployed at that commit until a human refreshes the secret.
The check names that cause instead of leaving a bare Alembic error, and it
fails rather than skips so the promotion gate stays honest.
Record that GitHub holds one pending run per concurrency group across both
workflows, so a queued bootstrap can be cancelled by a newer queued deploy and
needs dispatching again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PtexagKz2Si6SVLZoFKrz
* fix(ci): stop the recovery recipe from blanking the staging secret
The printed recipe piped modal run through grep straight into gh secret set.
gh reads stdin verbatim and never checks it for emptiness, and the value is
sealed client-side so the API cannot check it either. A modal run that failed
or emitted no URL therefore replaced STAGING_DATABASE_URL with an empty
value, and the pipeline still exited clean because the status came from gh.
The recipe now captures the URL, checks it is non-empty, and only then writes
the secret and dispatches the deploy. It uses a guard rather than pipefail,
because the recipe is pasted into an operator's own shell and must not change
that shell's options.
Correct the emit script docstring to match: modal run writes its own progress
output around the URL, so the line has to be selected and checked before any
secret is written.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PtexagKz2Si6SVLZoFKrz
* fix(ci): stop restarting staging compute that is already correct
The compute-return step applied the ci_large addon on every bootstrap run.
It never read the current tier, so its claim of being a no-op described the
intended end state and not the code. Applying an addon restarts the branch,
which put a restart of unknown duration in front of the deploy this run
dispatches, on every run rather than only after manual tier drift.
Read the current tier and send nothing when it already matches. Only a tier
that positively reads as ci_large skips the request. A failed read falls
through and applies, which is the behaviour this step has always had.
Retry the staging deploy credential check instead of probing once. A real
tier change still restarts the branch, and this project has seen connections
refused for seconds after health reports green, so a single attempt blamed a
stale credential for a database that was merely coming back. The check now
retries for 300s and its failure message names both causes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PtexagKz2Si6SVLZoFKrz
* fix(ci): keep the live staging secret off a mid-teardown database
Publishing oddish-staging-db right after the password reset gave the secret
the live app mounts a working credential while the schema was being replaced
and the tables reloaded. A cold start could then authenticate against a
half-built database, where before the reset left the app failing closed.
Split the two jobs that publish was doing. A recovery copy of the URL goes to
oddish-staging-db-pending immediately, which nothing mounts, so the new
password stays recoverable if a later step dies. The live secret is published
only after the mirror completes. The emit script now takes a secret name from
STAGING_DB_SECRET, and the printed recipe reads the pending copy.
Run the branch sync guard on staging pushes too. Rewriting staging can drop
main's ancestry without any push to main, and that path waited for the daily
cron.
Fail the staging deploy credential check immediately on a rejected password.
Retrying a deterministic rejection held the shared concurrency lock for five
minutes and delayed the recovery deploy. Refusals, timeouts and a starting
database still retry to the deadline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PtexagKz2Si6SVLZoFKrz
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(ci): preview deployment gate fixes (#1074)
* fix(ci): create the preview deployment record as the workflow
* fix(ci): record the preview deployment against the head commit
* feat(ci): skip preview provisioning for promotion pull requests
* fix(ci): promotion short-circuit requires the repository's own staging branch
* Enable opencode on closed-internet trials (egress allowlist) (#1030)
* Enable opencode on closed-internet trials via egress allowlist
Stock opencode self-installs (nvm/Node/opencode-ai) at trial start and
declares no Oddish egress, so on allow_internet=false trials Harbor's
Modal firewall blackholes both its install and its model calls: the trial
dies at DNS during agent setup ("curl: (6) Could not resolve host:
raw.githubusercontent.com") before the model is ever reached.
Add an OddishOpenCode wrapper implementing required_outbound_domains
(install-bootstrap hosts + model transport host via outbound_hosts_for_model,
which already resolves OpenRouter -> openrouter.ai), and route `-a opencode`
through it in _build_agent_config. Mirrors the AzureCompatibleCodex fix (#416).
Adds routing + egress unit tests and a CHANGELOG entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M4FaAPiaXnXCZzDJ8F1bvw
* opencode egress: forward per-trial kwargs to host resolution
Address Cursor Bugbot (medium): required_outbound_domains accepted kwargs but
never forwarded them to outbound_hosts_for_model, so a transport host pinned via
kwargs["extra_env"] (e.g. a custom OPENROUTER_BASE_URL) was not allowlisted and a
closed-internet trial overriding its base URL would still be firewalled after
install. Pass agent_kwargs=kwargs, mirroring AzureCompatibleCodex. Adds a
regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M4FaAPiaXnXCZzDJ8F1bvw
* Register opencode install hosts in _AGENT_RUNTIME_HOSTS — the enforced egress path
required_outbound_domains has no consumer in oddish or harbor (verified at
harbor 504c2518 and end-to-end on the PR preview backend: a closed-internet
opencode trial still died at nvm DNS with only the hook declared). The
allowlist actually enforced on closed-internet Modal trials is built by
_inject_restricted_agent_model_hosts as outbound_hosts_for_model (which
already resolves openrouter/<model> -> openrouter.ai) ∪ agent_runtime_hosts,
which reads the static _AGENT_RUNTIME_HOSTS registry. Register
OPENCODE_INSTALL_HOSTS there, keyed by both the stock agent name and the
OddishOpenCode wrapper class name (wrapper routing nulls the name). The hook
stays for interface parity, documented as declarative-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQug9XcmqY2aL5pmjJdRUN
* Route opencode install+model hosts through the environment baseline, mirroring claude-code
The agent-runtime-hosts registration could never work: opencode installs
during agent SETUP, which runs under the environment baseline — the
agent-phase allowlist only applies around agent.run() (verified e2e on the
preview: trials -1060/-1061 still died at nvm DNS with the registry entry
deployed). Mirror the claude-code installer arm instead: merge
OPENCODE_INSTALL_HOSTS + outbound_hosts_for_model into
env_config.extra_allowed_hosts, which harbor folds into the environment
baseline spanning install and run. This also covers legacy closed tasks
([environment] allow_internet=false -> no-network baseline, no dynamic
restricted agent phase — the GDM SWE-Marathon sample shape), where no other
channel grants even the model host. On modern swe-marathon-shaped tasks the
public baseline ignores the extras, so no install hosts leak into agent run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQug9XcmqY2aL5pmjJdRUN
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Pratyush Shukla <ps4534@nyu.edu>
---------
Co-authored-by: Andre Fu <39042250+andre-fu@users.noreply.github.com>
Co-authored-by: Pratyush Shukla <ps4534@nyu.edu>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|| { echo "::error::Staging Deploy is not green on $target"; exit 1; }
47
+
|| {
48
+
echo "::error::Staging Deploy is not green on $target"
49
+
echo "::notice::A queued deploy is superseded when a newer commit lands (GitHub keeps one pending run per concurrency group), so a commit that staging moved past may never have deployed. A dispatched deploy always runs the tip of staging, not an older commit, so promote the staging tip instead of this sha."
50
+
exit 1
51
+
}
48
52
else
49
53
echo "::warning::Staging Deploy workflow not found — skipping deploy-green precondition (bootstrap)"
0 commit comments