Skip to content

Codex/260729 security md reporting path - #799

Closed
batuchek68-ux wants to merge 58 commits into
lidge-jun:codex/260728-tls-altname-diagnosisfrom
batuchek68-ux:codex/260729-security-md-reporting-path
Closed

Codex/260729 security md reporting path#799
batuchek68-ux wants to merge 58 commits into
lidge-jun:codex/260728-tls-altname-diagnosisfrom
batuchek68-ux:codex/260729-security-md-reporting-path

Conversation

@batuchek68-ux

Copy link
Copy Markdown

Summary

  • Explain the user-visible or maintainer-facing change.

Verification

  • List the commands or checks you ran.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Wibias and others added 30 commits July 27, 2026 17:37
Persist a default-OFF archived cleanup policy, evaluate it on startup/schedule/manual runs via Phase 2 helpers, and surface controls on the Storage page.
Keep live config synced after background policy runs, execute exact
reduceToBytes candidate sets, preserve nextRun on unchanged schedules,
and harden the Storage policy UI against invalid drafts and raw errors.
Reject cleared thresholds, fail closed on malformed targets, tear down scheduler on shutdown, and defer startup policy off the microtask queue.
Move archive scan/FS/SQLite cleanup into a Bun Worker behind a single-flight job controller so POST /run returns immediately and /healthz stays responsive.
Replace the 100-file FS fixture with synthetic over-select math plus a small exact-path check, and raise timeouts on concurrent satellite restore cases that measure multi-second on Windows runners.
…tion

Reload persisted policy before saving lastRun/nextRun so concurrent PUTs during a long worker run are not silently reverted.
Abort/reset now cancel the active worker promise and generation-guard late completions; GUI distinguishes already_running, aborts poll on unmount, and test-stream returns 404 when disabled.
…-policy

feat(storage): opt-in auto-cleanup policy (phase 3, lidge-jun#42)
…idge-jun#491)

* fix(oauth): stop OAuth login from deleting a stored provider API key

`upsertOAuthProvider` replaced the provider entry with the bare preset on
every OAuth login. For providers that can be billed either way
(`allowKeyAuthOverride`: xai, github-copilot) this deleted `apiKey` and
`apiKeyPool` from config.json and silently flipped an explicit
`authMode: "key"` billing choice back to the subscription — the key was gone
and had to be pasted again.

Carry the key fields over and keep an explicitly chosen `authMode: "key"`,
so logging in never changes which credential bills the request. OAuth-only
providers and fresh logins still get the untouched preset.

* fix(oauth): restore OAuth mode after final key removal

* fix(oauth): promote safe pool key and keep key mode when authMode is omitted

Address review follow-ups so OAuth login preserves key billing when a usable key remains, including pool promotion and shared API-key sanitization.

* fix(oauth): sync active key into pool and gate key mode on resolved env

Keep list/switch/remove aligned with routing after OAuth upsert, and only restore authMode key when resolveEnvValue yields a usable secret.

* fix(oauth): push merged provider config to running proxy after CLI login

Avoid POST /api/providers with the bare OAuth preset, which dropped preserved apiKey/authMode and wrote that stripped state back to disk.

* fix(oauth): persist key intent without CLI env and route env keys at runtime

Stop upsertOAuthProvider from gating authMode on resolveEnvValue in the login CLI; router falls back to OAuth when the proxy cannot resolve an env-backed active key without rewriting stored mode.

---------

Co-authored-by: wonsh42 <wonsh42@users.noreply.github.com>
Co-authored-by: wonsh42 <300028278+wonsh42@users.noreply.github.com>
Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
The .codexclaw/ goalplans and ledgers are per-machine agent state. They were
committed with 'git add -f' despite the ignore rule, and once tracked the rule
stopped applying, so they rode along into main and preview.

Untrack them (files stay on disk), drop two .DS_Store files that got in the same
way, and add tests/repo-hygiene.test.ts so a forced add fails CI instead of
landing silently. Also drop a registry.ts comment pointing at a .codexclaw
evidence file that was never committed and cannot be resolved by any reader.
* feat(cli): add `ocx opencode` launcher

opencode reads providers from a JSON config rather than env slots, so the
`ocx claude` env-injection pattern does not transfer. This adds a launcher that
generates a provider block from the proxy's visible catalog and points
OPENCODE_CONFIG at it.

The user's own opencode.json is never written to. Their effective config is read
(explicit OPENCODE_CONFIG first, then the XDG global path), merged forward into
a generated copy under the opencodex config dir, and only the `opencodex`
provider key is overwritten. Carrying the base config forward keeps the command
correct whether opencode merges the OPENCODE_CONFIG layer or replaces it, and
leaves plain `opencode` completely unchanged.

Credential handling: the generated file is written to disk and outlives the
child, so it carries opencode's documented `{env:VAR}` reference instead of the
admission key, and the real value is passed only through the child environment.
The key resolves OPENCODEX_API_AUTH_TOKEN before config.apiKeys, matching
fetchClaudeContextWindows — a non-loopback bind requires the env token and may
have no apiKeys at all, where a placeholder would 401 every request.

Model limits: limit.context is emitted only from an authoritative context
window, including native slugs via nativeOpenAiContextWindow. opencode's schema
rejects a limit block carrying context without output and CatalogModel has no
authoritative output field, so a documented budget rides along, clamped to the
context window so a small-context model is never emitted with output > context.

Robustness: opencode.json is parsed as JSONC (strict JSON first, tolerant only
on failure) because opencode documents that syntax and a commented config would
otherwise be rejected as malformed; the generated file is written atomically so
a concurrent launch cannot read a torn file; the detached proxy-start child gets
an error listener so a failed spawn reports through the health poll instead of
throwing; and a project-level opencode.json defining provider.opencodex is
detected and warned about, since opencode loads that layer last and it outranks
the generated block.

Verified against a live proxy: 76 models registered, 47 carrying authoritative
limits with no output > context violations, `opencode models opencodex` lists
all of them, and an end-to-end run through opencodex/kiro/claude-haiku-4.5
returns a completion.

* fix(cli): harden ocx opencode launcher review follow-ups

Merge inherited OPENCODE_CONFIG_CONTENT instead of replacing it, probe the
live proxy hostname for baseURL, resolve admission from env/service token/
config keys, add x-opencodex-api-key on non-loopback binds, omit native slugs
in Codex Direct mode, and widen global override detection. Stabilize the
Windows storage-policy concurrency test timing.

* fix(cli): pass service token file to ocx opencode auto-start

When ocx opencode spawns a detached ocx start and OPENCODEX_API_AUTH_TOKEN
is absent, propagate OCX_API_TOKEN_FILE (or the default hardened path) so
handleStart can load the service token before a non-loopback bind.

* fix(cli): address CodeRabbit opencode review follow-ups

Use a deterministic loopback default config in provider-block helpers instead
of loadConfig(), send proxy admission via apiKey on loopback and only
x-opencodex-api-key on non-loopback binds, and split the docs/help examples
accordingly.

* fix(cli): build ocx opencode catalog from proxy /api/models

Fetch the live model list from the running proxy instead of calling fetchAllModels in the CLI process, so env-backed provider keys resolve in the proxy environment and namespaced selectors carry display metadata.

* fix(cli): bound ocx opencode /api/models fetch deadline

Abort stalled proxy catalog requests after 8s so ocx opencode fails fast instead of hanging before OpenCode launches.

---------

Co-authored-by: mihneaptu <223163739+mihneaptu@users.noreply.github.com>
…dge-jun#42) (lidge-jun#558)

* feat(storage): restore quarantined archived sessions (phase 2.1 of lidge-jun#42)

Add trash list/restore APIs, Storage GUI quarantine panel, and round-trip tests so default cleanup can be undone without Phase 3 purge.

* fix(storage): address PR lidge-jun#558 restore review and GUI loading races

Keep satellite backups for quarantine restore, snapshot full thread rows and state dependents, remap satellite DB paths, and make satellite failures retryable. Drop duplicate trash probing so Storage abort/generation ownership stays correct.

* fix(storage): harden restore moves and Windows lock-test timeouts

Use link+unlink no-replace for trash restore so a raced dest cannot be overwritten, and raise timeouts for multi-satellite cleanup tests that overrun 5s on Windows CI.

* fix(storage): atomic trash parse and legacy thread restore

Reject malformed manifests wholesale, gate stage deletion on restore completeness, and reconstruct production thread rows from rollout session_meta when satellite snapshots are missing.

* fix(storage): release memories lock before post-image backup write

Holding BEGIN IMMEDIATE across writeFileSync let Windows CI disk/AV latency stall the watermark concurrent-restore test past bun's 5s default. Persist consolidatePostImage after COMMIT and raise the two concurrent restore test budgets to match the other multi-satellite cases.

* fix(storage): compensate restore metadata on late failure

Restage files then delete rows this restore committed so satellite/leftover failures leave a clean retryable trash entry.

* fix(storage): enforce restore compensation success and preserve pre-existing rows

Track exact conflict-ignored inserts, require compensating DB txs to commit before restaging, and leave accurate partial counts when compensation is busy.

* fix(storage): resume incomplete restore instead of non-atomic compensation

Late failures after file moves now persist restore-pending.json and keep restored files in place, so retries accept destinations and finish only missing metadata without deleting rows before a guaranteed restage.

* fix(storage): write restore-pending atomically before file moves

Persist the resume marker with stage-local temp+fsync+rename before any rollout leaves quarantine, distinguish missing/valid/invalid markers, and fail closed when a resume still owes satellite work but satellite-backup.json is gone.

* fix(storage): keep placed dests on mid-move restore failure

Once restore-pending is durable, a later rename failure must not reverse successful moves or drop the planned acceptedDestRels marker so resume can finish staged files.

* fix(storage): close PR lidge-jun#558 restore blockers (fail-closed resume, tombstone finalize, worker job)

Fail closed per owed satellite section on resume, finalize successful restores via non-listable tombstone rename, and run restoreTrashEntry in a serialized Bun Worker so the event loop stays responsive.

* fix(storage): serialize cleanup and restore via shared mutation gate

Route manual cleanup and trash restore through one per-CODEX_HOME coordinator that returns 409 storage_mutation_busy instead of queuing. Stub runPolicyStorageMutation for Phase 3 policy worker integration.

* fix(storage): export policy overlap guard for pending restore

* fix(storage): surface restore worker failures and gate test-stream route

Map worker rejections to distinct restore error codes end-to-end (API, GUI, i18n) instead of generic restore_failed, and register the restore test-stream endpoint only when OPENCODEX_CLEANUP_TEST_HOOKS=1.

* fix(storage): drop broken pending-overlap hooks from cleanup preview

Keep only restore worker error types in cleanup.ts; remove calls to undefined pending-restore guard helpers introduced in the prior commit.

* fix(storage): guard cleanup against pending restore overlap

Reject cleanup when selected archives overlap acceptedDestRels from valid restore-pending markers, fail closed on unfinished satellite sections, and address tip CodeRabbit nits (healthz flake, preview status).

* fix(tests): stabilize Windows CI storage policy and rollback cases

Raise injected satellite rollback test timeout for slow Windows SQLite reconcile, and delay concurrent policy PUT until the worker has loaded the enabled snapshot inside holdAfterLoadMs.

* fix(storage): hold policy mutation slot in parent and backfill pending-safe selection

Acquire the shared CODEX_HOME mutation slot in the policy job controller before spawning the worker and release it on success, failure, timeout, and abort. Pending-restore destinations no longer consume percent or reduceToBytes selection budget; cleanup backfills with the next oldest safe archives. Adds API regressions for policy, restore, and manual cleanup contention in both orderings.

* fix(storage): clear policy-job inflight after busy-path settle

The request handler assignment overwrote executeJob's inflight=null on mutation-busy returns, leaving a settled Promise latched forever. Clear inflight in a finally on the assigned job promise instead.
Based on the original report and patch from @Aciredy. Removes Cursor user/developer prompt mutation for shell-alias hints (guidance stays in the system note) and rejects empty/invalid shell_command and exec_command payloads before they reach the bridge.

Co-authored-by: Aciredy <254986922+Aciredy@users.noreply.github.com>
isLoopbackRequestHost() required the Host header's port to equal the
proxy's own port, so a proxy reached through `ssh -L 20100:localhost:10100`
arrived as `Host: localhost:20100` and was refused. That gate sits in
front of the whole data plane — /v1/models, /v1/responses, /v1/messages,
/v1/chat/completions, /v1/live and the Responses WebSocket upgrade — not
just CORS, and it fires with no Origin header at all, so Codex CLI,
Claude Code and curl all saw a proxy that looked completely dead rather
than one with a CORS problem.

Loopback is a trust boundary by hostname, not by port. The sibling
isLoopbackOriginValue() dropped its own port check for exactly this
reason in e4e0612. Port equality was never the rebinding defense
either: a rebinding browser connects to the real port and sends it
verbatim, so the hostname check is what rejected it before and still
does. Verified against 40 Host forms — localhost.attacker.com,
127.0.0.1.nip.io, localtest.me, 0.0.0.0, 127.0.0.2, [::ffff:127.0.0.1]
and a Cyrillic homograph all stay refused.

Also accepts the FQDN form `localhost.`, which curl sends verbatim and
which was refused for the same class of reason.

The predicate had no test coverage at all. The new file pins both
directions, including a characterization test for the pre-existing
fail-open on an unparseable Host so tightening it later needs a
deliberate failing test.

Docs: forwarding is now documented, with the caveats that a forwarded
loopback is unauthenticated (ssh -g / container publishing expose it),
that the client base URL must be set by hand, and that provider OAuth
login needs its own forward.
…store (lidge-jun#571)

* fix(storage): harden lidge-jun#558 satellite backup writes and zst restore

Make every satellite-backup.json update crash-safe (tmp + fsync + rename +
best-effort dir fsync) so a failed post-memories rewrite cannot truncate the
last valid snapshot. Restore legacy compressed-only quarantine entries by
decompressing a lone .jsonl.zst in memory and reconstructing the thread row.

* fix(storage): harden satellite-backup write retries for lidge-jun#558 follow-up

Loop short writes to completion and rename via renameAtomicFile so Windows
sharing violations cannot replace a durable temp with a truncated backup.
)

* test(storage): cover restore test-stream route gate cases

Address CodeRabbit on lidge-jun#571: assert JSON 404 when the stream hook is null
under OPENCODEX_CLEANUP_TEST_HOOKS=1, and that the enabled stream path returns
the text/plain chunked response.

* test(storage): isolate restore test-stream route env and hooks

Reset OPENCODEX_CLEANUP_TEST_HOOKS and restore-job hooks in a nested finally
so enableTestStream cannot leak across cases in the responsive suite.
…idge-jun#493)

Anthropic OAuth usage is per credential: probe each logged-in account for 5h/weekly bars, use canonical fiveHour fields, and refresh overview quotas after active-account switches.

Maintainer takeover on latest dev with CodeRabbit/Codex/Wibias review harden (unavailable+TTL, local-cli fail-closed, health rebuild, probe sharing, GUI/docs, cache/inflight/login invalidation, async account-list load).

Credit: @wonsh42 (original feature).

Co-authored-by: wonsh42 <wonsh42@users.noreply.github.com>
Co-authored-by: wonsh42 <wonsh42@users.noreply.github.com>
Make Kiro Add-account run a transactional kiro-cli browser login with durable session recovery, account-scoped routing metadata, and generation-safe refresh.

Co-authored-by: coseung2 <120152615+coseung2@users.noreply.github.com>
Gemini chat image models emit authenticated opaque artifact URLs via responseModalities, with CCA Images fallback hardening and focused regressions.

Maintainer takeover on latest dev with CodeRabbit/Codex/Wibias review harden (admission bearer CCA path, n=1 + RECITATION, base64/magic validation, explicit allowlists, artifact HTTP route, size caps).

Credit: @tizerluo (original feature).

Co-authored-by: tizerluo <192086140+tizerluo@users.noreply.github.com>
lidge-jun#577)

Credit: @tizerluo for the original feature on lidge-jun#424.
Folds stacked lidge-jun#528 hardening and maintainer review/security follow-ups.
Reconciled with lidge-jun#355 artifact helpers on latest dev.

Closes lidge-jun#424
Closes lidge-jun#528
…dge-jun#578)

* feat(anthropic): opt-in Claude OAuth account pool (lidge-jun#294)

Add experimental, default-off routing across stored Anthropic OAuth accounts:
sticky session affinity, 429 cooldown failover, and new-session lowest 5h-usage
pick. Includes GUI toggle with an explicit not-battle-tested warning, management
API, docs, and focused regressions.

* fix(anthropic): address pool review feedback for lidge-jun#294

Return 429 when all accounts are cooling, bound per-request failover, skip unsafe local-cli refresh, and keep affinity/GUI/docs aligned with the reliability contract.
…#579)

* fix(release): baseline preview notes on last prior release

Preview generate-notes was channel-isolated (preview to preview only), so cutting 2.7.43-preview after a stable 2.7.42 with no matching preview restated the stable changelog. Preview baselines now use the newest prior stable or preview tag; stable still baselines prior stable and carries matching preview notes.

* fix(release): SemVer-order prior tags for notes baseline

Exclude tags newer than the target and rank stable after matching
previews so generate-notes does not pick the wrong previous release.
…back-gate

fix(server): treat forwarded loopback ports as loopback
…dge-jun#563) (lidge-jun#580)

* feat(dashboard): drain-and-restart from memory observability card

Expose a longer 60s informed recycle on the memory card (POST /api/system/restart) that reuses drainAndShutdown and respawns via ensure/service without Codex teardown (lidge-jun#563).

* fix(dashboard): harden drain-and-restart respawn and exit cleanup

Address Codex review: use failure-exit for supervised service children, spawn start with the live port (not ensure), suppress injection teardown on recycle, and clear the GUI draining state on pid change.

* fix(dashboard): arm drain immediately and harden reconnect polling

Set draining before the 200ms flush delay, bound reconnect /healthz polls, announce restart status to assistive tech, and clarify docs around active-turn drain timeout abort.
…un#584) (lidge-jun#585)

* fix(codex): same-request pool failover on pre-stream 429/402

Retry exhausted primary accounts once on an eligible alternate within the same turn, and rebind over-threshold sticky threads immediately so Codex CLI does not stall when a secondary still has quota (lidge-jun#584).

* fix(codex): attribute pool-retry transport failures to alternate account

Also classify HTTP 402 as quota so same-request failover cools the depleted account consistently with 429.

* fix(codex): keep subagent quota health scoped to the retried account

After a successful same-request pool failover, update subagentFallbackAccountId so streamed terminal 429s attribute health to the account that actually served.
… follow-up) (lidge-jun#594)

* fix(server): exit cleanly when drain-and-restart spawn fails

Follow-up to lidge-jun#580: wait for detached start spawn success, and on sync throw or pre-start error exit(1) without markRecycling so we never leave a drained process latched or exit(0) with no replacement.

* fix(server): sanitize restart spawn errors and restore fences

Address Codex review on lidge-jun#594: log only errno codes (no pathful messages), and clear inherited OCX_SERVICE on spawn failure so ensure/tray daemons still run syncCleanup restore.
* chore(gui): restore React Doctor 100 and gate CI on findings

Clear the post-cleanup regressions, pin react-doctor 0.9.2, and fail CI/prepush on any doctor finding so the score cannot silently drift again.

* fix(gui): address CI/Codex follow-ups for React Doctor gate

Keep Response-like test doubles working, preserve Grok/message error text, honor reconnect deadlines on non-OK healthz, and soft-skip offline npx/registry failures without letting real findings through.

* fix(gui): address CodeRabbit follow-ups on doctor gate PR

Extract boundedSignal, serialize Claude Desktop status polls, keep storage percent as a draft string, map trash list errors through i18n, and tighten React Doctor workflow assertions to active YAML.

* fix(gui): tighten doctor offline classifier and Grok apply errors

Make apply failures keep server message text, cover the fetch-json fallback path, and only soft-skip prepush on concrete registry/network signatures so findings with words like network still gate.

* fix(gui): bound polls and harden doctor prepush gate

Restore manual timeout fallbacks via shared createBoundedFetch, raise doctor maxBuffer and fail on ENOBUFS, recognize ECONNRESET registry failures, and tighten action SHA pinning assertions.

* fix(gui): match nonterminal npm ERR! soft-skip signatures

Give npm ERR! its own alternative so trailing whitespace after the bang still counts as an offline/registry failure instead of gating the push.

* fix(gui): treat stdio maxBuffer overflows as doctor gate failures

Also recognize Node's ERR_CHILD_PROCESS_STDIO_MAXBUFFER so oversized doctor output cannot soft-skip the prepush findings gate.

* fix(gui): run React Doctor via spawnSync with explicit maxBuffer

Replace execFileSync so prepush classification uses spawnSync status/error channels and cannot soft-skip oversized doctor output.

* test(gui): cover bounded fetch and doctor maxBuffer hard-fail

Add regression tests CodeRabbit asked for, and check AbortSignal helpers with typeof so the manual timeout fallback actually runs when any/timeout are missing.
…idge-jun#600)

Stop accepting bug reports that only fill Summary — empty or ellipsis-only Reproduction must fail the issue-quality gate (lidge-jun#598).
…idge-jun#597)

* feat(kimi): forward prompt_cache_key on the Kimi Coding Plan presets

Kimi's Chat Completions API documents prompt_cache_key as required for
Kimi Code Plan cache hits (a stable session/task id, unchanged across
exit and resume). Opt the canonical `kimi` OAuth and `kimi-code`
API-key presets into the existing openai-chat forwarding flag so the
caller-supplied key reaches https://api.kimi.com/coding/v1.

The adapter never invents a key: it forwards what the internal request
already carries (Codex's session key on /v1/responses, or the
session-scoped key the Claude /v1/messages inbound derives), an absent
field stays absent, and an explicit provider-level
`promptCacheKey: false` still opts out. All other OpenAI-compatible
presets remain deny-by-default because strict backends reject the
OpenAI-specific field.

Also persist the flag through providerConfigSeed /
enrichProviderFromRegistry like the sibling scalars
(parallelToolCalls, modelSuffixBracketStrip): key-pool 429 rotation
rebuilds the provider from the persisted config rather than the routed
one, so without seeding the retried request would silently drop the
key on exactly the quota-sensitive turns that need affinity most.

Evidence: https://platform.kimi.com/docs/api/chat

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Preserve Kimi prompt cache key on failover

* fix(server): inherit routed provider config across 429 key rotation

Generalize the promptCacheKey preservation from the previous commit: the
narrow registry backfill in rotateProviderTransportOn429 covered only that
one scalar, while every OTHER registry backfill routedProviderConfig merges
at request time was still lost when the four 429-failover sites in
src/server/responses/core.ts assigned the persisted-config snapshot to
route.provider wholesale. Concretely: kimi-code's noTemperatureModels /
modelReasoningEfforts / modelSuffixBracketStrip merges, NVIDIA NIM's
parallelToolCalls: false, and a registry-pinned baseUrl all silently
reverted on the rotated retry and later continuations in the turn.

rotateProviderTransportOn429 now takes the request's routed provider and
swaps ONLY the API key onto it before re-applying transport metadata,
mirroring the OAuth-401 replay path (which already spreads route.provider).
The registry lookup becomes unnecessary and is removed. Other failover
paths were audited and are sound: Codex multi-account retry strips runtime
fields off route.provider, and the Anthropic account pool spreads
route.provider directly.

Regression coverage: the existing Kimi unit test keeps asserting the wire
body on both attempts; a new unit test proves arbitrary registry-backfilled
fields survive rotation; a new e2e test on the kimi-code preset asserts
prompt_cache_key is present on BOTH the initial attempt and the
post-rotation retry (verified to fail against the pre-fix behavior).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lidge-jun and others added 22 commits July 28, 2026 21:24
…jun#614)

* docs(devlog): live state sync unit for 2026-07-28 (branches, PRs, issues)

Measured snapshot of origin/dev=7710185c0, 15 open PRs, 27 open issues, and
the delta against the 260727 owner-decision ledger and 260728 bug-bundle plan.

The audit round demoted two items the first draft got wrong: lidge-jun#570 is only
partially fixed (items 1a/2 of its six-item hardening plan; the myhost.lan alias
case still 403s), and lidge-jun#612 is credential-handling work under MAINTAINERS.md's
security-review rule rather than a decision-free patch.

* docs(devlog): rebuild-unit roadmap — screen 16 open PRs, lock 4 work-phases

Two audit rounds moved this a long way from the draft. The conflict set for lidge-jun#576
was inverted (logic files, not i18n), the proposed usage-debug size gate would have
elided 0.9% of reads, and lidge-jun#610's P1 turned out to be author-resolved while the same
defect survived on dev's own test.

* docs(devlog): record the WP2 measurement — the lidge-jun#610 P1 does not reproduce

PATH="" never reaches the child because runCodexDebugModels calls execFile
without an env option, so the launcher keeps working and the catalog loads.
The isolation that line intends is decorative; the test stays safe through
CODEX_CLI_PATH instead. Closing WP2 as NOOP with the observation sent to lidge-jun#610.

* test(usage): give the rolling-file bound test room on Windows

The 325 appends this test needs cost ~1,954 synchronous fs calls (measured:
mkdir 325, chmod 652, append 325, exists 325, read 325, write 2). On
windows-latest under full-suite load that took 13.6s and tripped the 5s default,
while ubuntu and macos stayed well under it.

The cost is per-open rather than per-byte, so removing one of the six calls would
not close a 2.7x overshoot. Reducing the append count would trade away coverage --
325 is already the minimum that crosses the rotate threshold twice. Both
assertions and the rotation contract are unchanged.

* docs(devlog): record the lidge-jun#576 rebase outcome and the regression it surfaced

The three conflicts were the predicted logic files. What the audit caught was
separate: the pre-rebase commit already deleted dev's grokSyncFailureMessage and
its three handlers, and no test in the repo asserts that string, so a full green
suite would not have stopped it.
…idge-jun#576)

A long-lived Codex app-server keeps serving its in-memory model list, so
ocx sync could write a correct catalog and Codex would still show the old
models (lidge-jun#476). Restarting the proxy or re-running sync did not help; only
killing the app-server did, which is why rebooting appeared to fix it.

Detect those processes and say so, gated on the catalogWritten/cacheSynced
signal from the first half of this change so a no-op sync stays quiet.
ocx sync --restart-codex and ocx sync-cache --restart-codex opt into a
SIGTERM, never SIGKILL.

Process matching is the risky part and is deliberately narrow: UID-scoped
on Unix, exact cmdline match rather than a broad *codex* sweep, and it
understands quoted paths and value-taking globals so it cannot mistake a
neighbouring process for an app-server.
The project is moving its primary runtime to the Go native port, so
dev2-go has to keep receiving everything that lands on dev. Nothing
changes for contributors: pull requests still target dev. The extra
step belongs to the maintainer who merges one — rebase the work onto
dev2-go, port whatever needs a Go counterpart under go/, and merge
that. The item is finished only when both lines carry the change.

An unported dev merge is the failure mode this rule exists to prevent:
dev2-go silently falls behind and the divergence surfaces later as a
conflict nobody has context for. So the port happens in the same
session as the merge; a change with no Go counterpart gets that
decision recorded; a port that has to wait gets a tracking issue
against dev2-go naming the source commits.

MAINTAINERS.md carries the authoritative wording and the other seven
files summarise it. The maintainer table widens both maintainers'
integration duty to dev and dev2-go, since that is what the rule
actually asks of them.

Also correct the 2026-07-27 change-log entry. It claimed requirement 2
(review by another current maintainer) would be satisfied "when this
change is reviewed and merged". It never was: a2693c0, dc3a4ad and
02bbd47 landed on dev as direct owner pushes with no associated pull
request. The addition is in effect regardless — @Wibias holds write
access and has been merging since 2026-07-26 — so the entry now
records the gap instead of asserting a procedure that did not happen.
Adds nested AGENTS.md files and CODEOWNERS coverage for agent guidance. Keeps the owner Transition intake rule already on dev (merge to dev, carry onto dev2-go, port go/ only when needed).
)

* feat(videos): add Grok video bridge for non-OpenAI models

Extends the image bridge to support asynchronous video generation via xAI
Grok Imagine Video. Video generation uses a submit→poll→download pattern
(POST /v1/videos/generations → GET /v1/videos/{id}) with heartbeat forwarding
to keep the SSE stream alive during the 30-180s generation window.

New files:
- src/images/xai-video-client.ts: submitVideoJob + pollVideoJob
- src/images/fulfill-video.ts: arg parsing, async polling generator, result builder
- tests/videos/: 32 tests (xai-video-client, fulfill-video, plan-video)
- docs-site/.../video-bridge.md: user guide

Modified files:
- src/images/loop.ts: unified image+video fulfillment, per-turn call caps
- src/images/types.ts: VideoBridgePlan/VideoCallResult shared types
- src/images/synthetic-tool.ts: buildVideoTool, VIDEO_GEN_TOOL_NAME
- src/images/plan.ts: planVideoBridge (opt-in via videoBridgeEnabled)
- src/images/artifacts.ts: downloadVideoToArtifact (200MB cap, SSRF-protected)
- src/server/responses/core.ts: video bridge wiring + tool dedup
- src/types.ts: videoGeneration flag, video config fields

Video bridge is opt-in (videoBridgeEnabled defaults to false). All upstream
image-bridge hardening preserved (SSRF, size caps, abort linking, pinned HTTPS).

* fix(videos): address Codex P1/P2 + CodeRabbit review findings

P1 fixes:
- Non-streaming requests no longer 400 when video bridge is enabled
  (only image bridge requires stream=true; video-only skips bridge for
  non-stream requests)
- Restore globalThis.fetch after video client tests (afterEach cleanup)

P2 fixes:
- Use clamped vidPlan.timeoutMs instead of raw config value
- Honor videoMaxRounds when both image+video bridges active (tighter wins)
- guessVideoExtFromMagic throws on unrecognized magic (was defaulting to mp4)
- Poll 4xx permanent failures (except 429) fail fast instead of retrying
- buildVideoResult uses pathToFileURL for cross-platform markdown links
- Initial poll interval restored to 5s (was 200ms)
- done-without-videoUrl returns error instead of spinning until timeout
- VideoBudget charges every streamed chunk (was only first chunk)
- downloadVideoToArtifact: reader cleanup on file-open failure
- readBoundedText: cancel reader body on size cap breach
- Namespaced video_gen tools preserved in dedup filter
- Fix JSON missing closing brace in docs
- Clarify API key auth requirement in docs prerequisites

* fix(videos): encode requestId, defer paidVideoCalls, guard plan! assertion

- Encode requestId in poll URL to prevent path injection (CodeRabbit)
- Move paidVideoCalls++ past arg validation so malformed calls don't burn budget
- Add early guard for undefined plan in image branch, removing non-null assertions

* fix(videos): batch-aware artifact pruning + clarify provider key docs

- Move pruneArtifacts from per-download to post-batch so multi-video turns
  don't delete earlier videos before tool results are injected (Codex P2)
- Document that video bridge requires providers.xai with apiKey authMode,
  not OAuth from 'ocx login xai' (Codex P2)

* fix(videos): web search coexistence, unified timeout budget, VideoBudget ceiling

Address Wibias CHANGES_REQUESTED review:

1. Web search coexistence (P1):
   - When wsPlan is active, media bridge is skipped (existing behavior)
   - Now emits console.warn so the user sees the skip instead of silent loss
   - Documented priority rule in video-bridge.md

2. Timeout budget (P2):
   - Start deadline BEFORE submitVideoJob, not after
   - Poll receives remaining budget (deadline - now), min 5s floor
   - Submit (60s) + poll now share one videoTimeoutMs deadline

3. VideoBudget aggregate ceiling (optional, addressed):
   - VideoBudget now has a cap (600 MiB = 3 × single-download max)
   - chargeVideoBudget() enforces the ceiling per-chunk during streaming
   - Exceeding the budget throws mid-download (partial file is cleaned up)

4. New tests: done-without-videoUrl, permanent 4xx poll stop, requestId encoding

* fix(videos): gate tool_choice on imgPlan, shared deadline signal, buffer magic sniff

Address Wibias second CHANGES_REQUESTED review:

Required:
B. Gate image tool_choice rewriting on imgPlan — video-only turns no longer
   rewrite image_generation/image_gen aliases to an undeclared tool (core.ts)
C. Shared timeout complete — deadline-bound AbortSignal passed into submitVideoJob;
   if budget expired after submit, poll is skipped with timeout error (no 5s floor)

Preferred:
A. Buffer ≥12 bytes before magic-byte sniff — accumulate chunks until sniff minimum
   so short first reads don't crash guessVideoExtFromMagic (artifacts.ts)

Optional:
D. Docs: scope web-search priority to runnable sidecar / non-runTurn path

* fix(videos): pass deadline-bound signal into poll generator

Pass linkedDeadline (not raw client signal) into pollVideoWithHeartbeats so
in-flight pollVideoJob fetches and sleep() calls abort when the wall-clock
budget expires. Abort error messages now distinguish deadline-expiry from
client-cancel based on elapsed time vs timeoutMs threshold.

* fix(videos): deadline abort classification, pruned-path guard, video aliases, DNS noun

Address Wibias fourth CHANGES_REQUESTED review:

1. Deadline abort classification (required lidge-jun#1):
   - Replace 0.95 wall-clock heuristic with signal.reason.name === 'TimeoutError'
   - deadlineSignal declared outside try so catch can check deadlineSignal.aborted
   - Submit catch returns timeout error when deadlineSignal fires (not generic abort)

2. Don't deliver pruned artifact paths (required lidge-jun#2):
   - After batch prune, scan fulfilled results and rewrite any whose path
     no longer exists to ok:false with explanatory error

3. Wire video aliases (required lidge-jun#3):
   - planVideoBridge seeds toolNames with VIDEO_GEN_TOOL_NAME + any request
     function tools matching isVideoGenName (image-bridge parity)
   - core.ts filter only strips unnamespaced video_gen aliases (namespaced
     MCP tools are left alone)

4. Fix test fixtures (required lidge-jun#4):
   - authMode 'api_key' → 'key' (valid OcxProviderConfig value)

5. DNS error noun (small):
   - resolvePublicAddresses accepts optional noun param (default 'image')
   - downloadVideoToArtifact passes 'video'

* fix(videos): complete pruned-path guard, skip namespaced aliases, mock sleep in tests

Address Wibias fifth CHANGES_REQUESTED review:

1. Finish pruned-path guard:
   - Filter files[] through existsSync, not just path
   - If all pruned: ok:false with no markdown/path
   - If some survive: refresh path/files/count/markdown from survivors

2. Skip namespaced tools when seeding videoPlan.toolNames:
   - if (t.namespace) continue — matches core.ts filter rule

3. Fix ubuntu CI timeout:
   - Mock setTimeout in heartbeat test so 5s poll interval resolves instantly
   - Suite now runs in ~156ms (was ~5s)

* fix(videos): stop hanging ubuntu CI with global setTimeout mock

cf76fb1 mocked globalThis.setTimeout to skip the 5s poll sleep. That races
parallel Bun workers and cancelled ubuntu-latest at the 12m job cap
(938f25e was green; cf76fb1 hung). Inject sleep/poll seams instead and drop
mock.module so video tests no longer poison each other.

---------

Co-authored-by: Your Name <your.email@example.com>
Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
* Merge remote-tracking branch 'origin/dev' into fix/codex-spark-quota-scope-dev

* fix: retain affinity per Codex quota scope

* fix: address scoped affinity review feedback

* fix: restore dev version and harden compact test
* feat(gui): show reset credit expiration time

* fix(gui): respect selected locale for credit dates
…#638)

* fix(adapters): neutralize Codex CLI 0.145 identity wording

Closes lidge-jun#622

* fix(adapters): consume full GPT-5.x.y identity versions
Maintainer takeover of lidge-jun#565. Persisted pause exclusion, bulk pause-exhausted, GUI controls, docs. Integrated with account namespaces and Spark quota scopes on current dev.
Name the tracking-issue label in MAINTAINERS; mirror it in the AGENTS Transition summary.
…lidge-jun#627)

* fix(cursor): steer Windows bridge shell away from PS 5.1 syntax loops

Native-shell rejection told the model to replay the same bash/CMD command through the Codex bridge, which on Windows PowerShell 5.1 fails and loops (lidge-jun#604). Adapt for the host shell and inject PS-safe guidance instead.

* fix(cursor): address lidge-jun#604 review — host-shell-neutral retry-stop guidance

Drop proxy-OS platform branching and `;`-as-`&&` advice; use if ($?) and one corrected bridge attempt for Codex/CodeRabbit feedback on lidge-jun#627.

* fix(cursor): state PowerShell 5.1 has no &&/|| in lidge-jun#604 guidance

CodeRabbit follow-up: make unsupported-parser-error wording explicit and assert it in rejection/guidance tests.
…e-jun#651)

Fixes lidge-jun#647. Single shared 401 token refresh gate; no prompt storm on concurrent /api/* 401s.
…gates (lidge-jun#631)

Hardens wrong-base PR drafting so the required check fails even when draft GraphQL soft-fails, and tightens bug-form Version/OS/reproduction gates.

Go port: none — CI/workflows and issue-quality scripts only; no Go counterpart.
* fix(catalog): accept Together top-level /models arrays

Closes lidge-jun#617

* fix(catalog): keep models[] for probe only; array+data for discovery
Fixes lidge-jun#618. Prefer findLiveProxy over ocx.pid alone; keep status/doctor read-only on malformed config; preserve authoritative null live pids and runtime vs config listen source.
…jun#643)

Fixes lidge-jun#636. Skip bare gpt-*/native OpenAI catalog rows when no enabled canonical openai provider exists; make NoEnabledOpenAiProviderError actionable.
…n#648)

* docs: spec PR quality gates for ancestry and descriptions

Capture the approved design for rejecting main-based PRs into
dev/dev2-go and empty/thin/malformed PR bodies in the enforcer.

* docs: plan PR ancestry and description quality gates

Break the approved spec into TDD tasks for pr-quality.cjs,
enforcer wiring, harness coverage, and contributing docs.

* feat(ci): add pure PR ancestry and description quality checks

* test(ci): cover wrong_base plus bad_description together

* feat(ci): enforce PR ancestry and description in target gate

* fix(ci): strip WRONG BRANCH prefix when base is corrected

* test(ci): cover PR ancestry and description enforcement paths

* docs: document PR ancestry and description quality gates

* fix(ci): address CodeRabbit findings on PR quality gates

Treat removed new-form Version/OS headings as missing, soft-fail
ancestry compares, tighten harness require/write allowlists, and
clarify enforce-target is convention until branch protection.

* fix(ci): address Codex findings on PR quality gates

Reject untouched PR templates, require low ahead-of-main for ancestry, and checkpoint draft/title ownership before mutations.
Co-authored-by: hanbinnoh <282618027+hanbinnoh@users.noreply.github.com>
Fixes lidge-jun#612. Async ACL runner for response-state writes; destination-path timeout memo; owner-first grant. Conflict-resolved onto current dig.
* fix(anthropic): reorder interleaved text before tool_use

Closes lidge-jun#620

* test(anthropic): rename wire body var for review clarity
Private vulnerability reporting was disabled on the repository, but
SECURITY.md told reporters to prefer it "when that option is available in
the repository UI" and stated no private security email exists. Every
private channel it named was therefore unreachable, leaving a public issue
as the only route for undisclosed vulnerabilities.

Private vulnerability reporting is now enabled. Replace the conditional
wording with the direct advisory URL, keep the minimal-public-issue text
as a fallback rather than the default, and surface the same link from the
issue-template chooser and README.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aa15a0dd-b3c4-410b-921c-b707b36e0c21

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@lidge-jun

Copy link
Copy Markdown
Owner

Closing. This pull request contains no work by its author.

The head commit here is byte-identical to a branch that already exists in this repository and was authored by the maintainer. All six of these pull requests have the same shape: fork the repository, push the upstream branches back unchanged, and open them as incoming contributions.

PR head SHA identical upstream branch
#798 eef9a86d codex/260728-tls-altname-diagnosis
#799 d0763bf7 codex/260729-security-md-reporting-path
#800 d380b1b0 codex/260731-pr-merge-round
#802 a64aa585 codex/app-server-restart
#803 b0434ea5 codex/pr533-update-recovery-hardening
#804 f0acc36a stack/gui-dense-workspaces

Every listed SHA resolves to the same commit on the upstream branch of the same name. The commit authors inside them are lidge-jun, Wibias, ahmetb, aljjang95, hanbinnoh and other existing contributors. The descriptions are the unmodified pull request template with every checklist item unchecked, and #798 additionally carries the maintainer's own commit message pasted in as if it were a summary. Several target main, which AGENTS.md reserves for maintainer-controlled promotion, and two are chained onto each other's heads in a cycle.

This is not a rebase, a resubmission of stalled work, or a fork that drifted. It burns maintainer review time and CI minutes on a diff that is already in the tree, and it presents other people's commits under a new author's pull request. Repository access is being revoked for this account.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.