Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
1a18ace
docs: add minimal container / agent sandbox instructions to AGENTS.md…
lidge-jun Aug 28, 2026
befcac3
fix(release): move dev's version line past the published preview
lidge-jun Aug 28, 2026
69031f6
fix(kiro): render one answer when prose and the completion tool share…
lidge-jun Aug 28, 2026
ab21fa5
Merge pull request #2836 from lidge-jun/codex/dev-version-line-2360
lidge-jun Aug 28, 2026
e234555
fix(catalog): drop default_verbosity when verbosity is unsupported
Aug 28, 2026
249e1fb
test(catalog): rename post-fix verbosity cases
adtumk Aug 28, 2026
7f812dd
fix(security): classify NAT64-embedded IPv4 instead of refusing the w…
olddonkey Aug 28, 2026
2869d2f
docs(devlog): close the Kiro duplicate-answer unit (#2838)
lidge-jun Aug 28, 2026
bf166fc
fix(responses): scope canonical system folding to messages (#2822)
luvs01 Aug 28, 2026
3058966
fix(cli): scope model removal selectors to provider (#2821)
luvs01 Aug 28, 2026
24f053b
fix(catalog): raise Muse Spark context window to 1M on OpenCode Go (#…
DevonGithub Aug 28, 2026
737748a
Merge pull request #2839 from lidge-jun/codex/lane-a-catalog-and-nat64
lidge-jun Aug 28, 2026
f7bb893
fix(ci): restrict closed-PR cleanup to disposable namespaces (#2840)
luvs01 Aug 28, 2026
a667071
fix(codex): close drain routing follow-ups
luvs01 Aug 25, 2026
a270954
fix(codex): defer unimplemented entitlement outage contract
luvs01 Aug 26, 2026
9010b5a
fix(codex): fence retry entitlement refresh
luvs01 Aug 28, 2026
da15d86
fix(codex): preserve main claims and model detours
luvs01 Aug 28, 2026
ced264d
fix(codex): address routing review follow-ups
luvs01 Aug 28, 2026
071787a
fix(grok): preserve retired orphan model tables
luvs01 Aug 28, 2026
c8534f6
fix(grok): close orphan lifecycle review gaps
luvs01 Aug 28, 2026
78b5366
fix(grok): harden orphan ownership classification
luvs01 Aug 28, 2026
57b5eef
fix(grok): make orphan cleanup TOML-safe
luvs01 Aug 28, 2026
0ed3f35
test(grok): assert retired orphan injection
luvs01 Aug 28, 2026
a52d00a
fix(codex): skip failing quota candidates
luvs01 Aug 28, 2026
ecd6225
fix(grok): clear references to removed models
lidge-jun Aug 28, 2026
4fc0f70
Merge pull request #2845 from lidge-jun/codex/wp9-2638-drain-routing
lidge-jun Aug 28, 2026
bb3321c
fix(agentrouter): support openai-chat identity and framing (#2843)
lidge-jun Aug 28, 2026
9abfabf
fix(grok): clean every removed model reference
lidge-jun Aug 28, 2026
c986d1d
fix(security): reach the fake-IP opt-in for explicit-zero mapped answ…
lidge-jun Aug 28, 2026
1a73b7a
fix(grok): clean inline subagent model references
lidge-jun Aug 28, 2026
d2e9920
fix(shadow-call): reject self-intercept targets (#2849)
lidge-jun Aug 28, 2026
8877df0
fix(responses): recover encrypted combo agent tasks (#2850)
lidge-jun Aug 28, 2026
ca8fd60
fix(codex): admit keyring-managed native credentials (#2718) (#2847)
lidge-jun Aug 28, 2026
2a44e3a
Merge pull request #2846 from lidge-jun/codex/wp9-2828-orphan-tables
lidge-jun Aug 28, 2026
5734a1c
fix(doctor): diagnose the broken Codex env_key launch path (#2844)
lidge-jun Aug 28, 2026
5a829b7
fix(responses): bind a rotated OAuth bearer to its own Copilot origin…
lidge-jun Aug 28, 2026
f726daa
docs(devlog): bug-PR zero-remaining campaign roadmap + executable rev…
lidge-jun Aug 28, 2026
582eb42
fix(shadow-call): close self-target persistence gaps (#2852)
lidge-jun Aug 28, 2026
e088d46
fix(responses): gate combo recovery on availability (#2851)
lidge-jun Aug 28, 2026
182b356
fix(codex): refresh expired native main auth.json tokens (#2221) (#2848)
lidge-jun Aug 28, 2026
577bce6
docs(devlog): close bug-PR zero-remaining campaign (#2855)
lidge-jun Aug 28, 2026
878d986
fix(windows): scope scheduler ownership by home (#2857)
lidge-jun Aug 28, 2026
42b88dc
fix(windows): keep installed tray alive after launch (#2856)
lidge-jun Aug 28, 2026
676a3c0
fix(responses): retry compact on routed handoff target (#2858)
lidge-jun Aug 28, 2026
e546c16
fix(gui): harden dashboard asset and log polling (#2859)
lidge-jun Aug 28, 2026
7b13e38
fix(cli): report an unclean prior proxy exit instead of a silent outa…
lidge-jun Aug 29, 2026
124a2b1
fix(codex): keep native eligibility metadata off routed catalog rows …
lidge-jun Aug 29, 2026
6ec79e4
fix(cursor): bound the assembled external replay envelope (#2865)
lidge-jun Aug 29, 2026
93b5507
test(cursor): prove reserved call-id codec closure (#2868)
lidge-jun Aug 29, 2026
8d1dc1f
fix(systemd): support legacy service managers (#2869)
lidge-jun Aug 29, 2026
d04f4f4
feat(test): add test:changed and make it the local check during imple…
olddonkey Aug 29, 2026
318dbd8
fix(cursor): authenticate call id provenance (#2876)
lidge-jun Aug 29, 2026
d82b304
feat(kiro): per-account quota display and quota-aware account pool (#…
lidge-jun Aug 29, 2026
db7606f
feat(router): opt-in blocked model redirection at shared routing laye…
terrytan95 Aug 29, 2026
7071b2d
feat(usage): add provider, model, and day cache metrics with price co…
chilung-cgu Aug 29, 2026
c4fdc5e
feat(grok): inject per-model reasoning effort into Grok Build config …
takltc Aug 29, 2026
2c2cda3
feat(combos): add random, least-used, and reset-window routing strate…
x3M3x Aug 29, 2026
aa5f711
feat(providers): add model-specific provider routing for Vercel AI Ga…
chilung-cgu Aug 29, 2026
e308f13
feat(xai): opt-in x_search alongside hosted web search on the Respons…
olddonkey Aug 29, 2026
b9cb236
feat(server): return trusted Responses request ids (#2827)
yamashirotakashi Aug 29, 2026
12fbb5b
feat(oauth): choose the account with known headroom before dispatch (…
lidge-jun Aug 29, 2026
6703aba
fix(windows): unblock the isolated fabric producer and unpin CI-scale…
lidge-jun Aug 29, 2026
3e3df05
feat(quota): keep per-account quota across restarts (#2880)
lidge-jun Aug 29, 2026
9b9a344
fix(windows): contain the fabric producer and remove timeout-only sta…
lidge-jun Aug 29, 2026
8621acb
docs(devlog): verify the three merged phases compose on dev (#2882)
lidge-jun Aug 29, 2026
de91dfd
fix(codex): complete prompt probe coalescing (#2872)
lidge-jun Aug 29, 2026
eb52973
fix(codex): match the shim's launcher backups in --restart-codex (#2888)
lidge-jun Aug 29, 2026
0f4cd2a
fix(codex): refresh and replay an ordinary pool 401 instead of quaran…
lidge-jun Aug 29, 2026
c3da277
fix(codex): ask upstream for the entitlement roster under a real clie…
lidge-jun Aug 29, 2026
4fa981f
fix(codex): bound the stored Pool 401 recovery budget by account (#2897)
lidge-jun Aug 29, 2026
8df7051
fix(cursor): name the invocation inside a replayed tool result (#2900)
lidge-jun Aug 29, 2026
27c6993
fix(cursor): bound the replayed invocation and compare full tool iden…
lidge-jun Aug 29, 2026
7d37c8a
fix(catalog): accept unified_exec rows and restore plugin usage instr…
lidge-jun Aug 29, 2026
22f2df6
fix(web-search): honor the provider upstream HTTP version pin on both…
lidge-jun Aug 29, 2026
fc74e20
fix(gui): reserve the sidecar hint in lines so the pair stays aligned…
lidge-jun Aug 29, 2026
4ec1cc9
fix(service): launch the systemd unit through the stable ocx executab…
lidge-jun Aug 29, 2026
4d646c4
fix(gui): cap the log table against the dynamic viewport and fix the …
lidge-jun Aug 29, 2026
e1becb7
docs(devlog): back 020's withdrawal with the horizontal measurement (…
lidge-jun Aug 29, 2026
11d3359
fix(cursor): index replayed tool calls from full history on the check…
lidge-jun Aug 29, 2026
a0a8e8e
docs(devlog): close the dashboard slop unit with its outcome (#2912)
lidge-jun Aug 29, 2026
cfb70c9
docs(devlog): enumerate every invocation-line site to bound the fix (…
lidge-jun Aug 29, 2026
fe05b0a
fix(service): harden stable systemd launcher contracts (#2916)
Ingwannu Aug 29, 2026
a05dd25
fix(providers): give the live GLM-5.3-Flash route its own effort ladd…
lidge-jun Aug 29, 2026
aaccef7
fix(service): decode CJK schtasks output and let the task listing fin…
lidge-jun Aug 29, 2026
6906049
docs(devlog): correct the red-test arithmetic and name two enumeratio…
lidge-jun Aug 29, 2026
64b994c
fix(gui): correct the record and lock the test constants (#2915)
lidge-jun Aug 29, 2026
8f199fc
fix(codex): scope refresh-flight cancellation to the caller that owns…
lidge-jun Aug 29, 2026
824a7af
fix(claude): detect Windows Desktop policy conflicts (#2924)
lidge-jun Aug 29, 2026
8404983
test(codex): prove replay 402 closes compact recovery (#2922)
lidge-jun Aug 29, 2026
1d9b389
fix(codex): share one identity-lookup budget between desktops and CI …
lidge-jun Aug 29, 2026
09a5029
fix(windows): reuse unchanged startup task listing (#2928)
Ingwannu Aug 29, 2026
112db9e
fix(combos): preserve all five combo strategies in dashboard and docs…
x3M3x Aug 29, 2026
2cb5093
docs(devlog): record the lane N units and their outcomes (#2930)
lidge-jun Aug 29, 2026
ae356a3
fix(gui): group and translate the newer combo strategies in the works…
x3M3x Aug 29, 2026
6a907d2
fix(upstream): apply fresh-connection recovery on the sidecar and loo…
lidge-jun Aug 29, 2026
d882cae
fix(cursor): require a replayed call to precede the result it names (…
lidge-jun Aug 29, 2026
dd159db
fix(cli): send the cleanup percent in the policy target the server re…
luvs01 Aug 29, 2026
3a9835c
fix(codex): reconcile the refreshed plan on the shared flight, not th…
luvs01 Aug 29, 2026
f5b8529
fix(codex): heal a dormant same-grant record and drop stale 401 evide…
lidge-jun Aug 29, 2026
f3393aa
feat(providers): native Ollama /api/chat transport with /api/show met…
adtumk Aug 29, 2026
5b573ac
feat(grok): migrate managed block to model_providers inheritance (#2890)
bet4it Aug 29, 2026
3700526
fix(catalog): read Copilot nested vision capability (#2943)
Ingwannu Aug 29, 2026
2e92c89
test(catalog): pin Copilot vision precedence and nested/flat denial e…
lidge-jun Aug 29, 2026
8427efe
fix(adapters): classify failed exec wrappers by forward scan, not bac…
lidge-jun Aug 29, 2026
47b8d16
test(codex): isolate the tombstone guard with a credential-carrying t…
lidge-jun Aug 29, 2026
dca1694
test: let the README asset check tell files from directories (#2952)
luvs01 Aug 30, 2026
b95dc5d
fix(test): scope test lock to user runtime (#2962)
lidge-jun Aug 30, 2026
4f6a196
fix(gui): give the provider toggle a real flex basis so the header ca…
lidge-jun Aug 30, 2026
209e9f4
fix(google): strip rejected Claude SDK paragraph (#2961)
lidge-jun Aug 30, 2026
de4e846
fix(responses): stop a namespaced MCP exec from authorizing bare shel…
lidge-jun Aug 30, 2026
dd3ff42
perf(windows): skip proven ACL mutations (#2963)
lidge-jun Aug 30, 2026
eeedbb6
fix(config): start when proxy settings hold values the schema never c…
lidge-jun Aug 30, 2026
62df78d
fix(cursor): keep checkpoint-suffix history intact through pruning an…
lidge-jun Aug 30, 2026
d4fe9ca
fix(test): install gui dependencies the local runner needs (#2964)
lidge-jun Aug 30, 2026
13ba8f1
docs(devlog): close the cursor tool-continuation unit into _fin (#2970)
lidge-jun Aug 30, 2026
d760f36
fix(responses): keep the empty-completion notice to one log record (#…
luvs01 Aug 30, 2026
41d7d4c
fix(windows): repair the two dispatch-only Windows test failures (#2968)
lidge-jun Aug 30, 2026
d2a8022
fix(quota): keep the capacity panel when a credit expiry cannot be fo…
lidge-jun Aug 30, 2026
7747bf7
docs(devlog): record the terminal outcome the unit close claimed (#2971)
lidge-jun Aug 30, 2026
c2b64db
docs(devlog): record the release-readiness train and its outcome (#2974)
lidge-jun Aug 30, 2026
aa16a71
test(windows): preserve external prompt path semantics (#2972)
lidge-jun Aug 30, 2026
4157593
docs(devlog): record the final gate for the cursor tool-continuation …
lidge-jun Aug 30, 2026
223a0a2
test(windows): close the four residual shard-2 Windows failures (#2975)
lidge-jun Aug 30, 2026
28c69b0
test(windows): tolerate loaded launcher startup (#2983)
lidge-jun Aug 30, 2026
015fedb
fix(quota): correct GLM Coding Plan auth and exclude MCP allowance fr…
lidge-jun Aug 30, 2026
f4d2862
fix(slug): resolve self-namespaced native ids before the provider-qua…
luvs01 Aug 30, 2026
8b8197d
feat(xai): expose grok-4.20-multi-agent on the Responses lane (carry …
lidge-jun Aug 30, 2026
628b8e8
fix(codex): bound the provenance ledger by bytes, not only by transac…
luvs01 Aug 30, 2026
641466d
fix(test): isolate per-user test run locks (#2984)
luvs01 Aug 30, 2026
8fba9d1
fix(adapters): annotate empty tool outputs, keeping an explicit provi…
lidge-jun Aug 30, 2026
f636763
feat(server): add least-privilege GET /v1/catalog for remote Codex cl…
lidge-jun Aug 30, 2026
607042b
feat(providers): add opt-in transient-5xx retry with a shared total-s…
lidge-jun Aug 30, 2026
46c3383
feat(anthropic): add quota-window account pool routing (carry of #256…
lidge-jun Aug 30, 2026
9cd4e4b
docs: correct Kiro pooling, combo strategies, and undocumented config…
lidge-jun Aug 30, 2026
3bd6873
fix(responses): keep the terminal-guard continuation on the shared tr…
lidge-jun Aug 30, 2026
3998bb4
test(responses): pin every transient-retry leg to the shared send budget
lidge-jun Aug 30, 2026
bb6a6fb
Merge pull request #2998 from lidge-jun/codex/shared-transient-budget…
lidge-jun Aug 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 19 additions & 1 deletion .github/scripts/closed-pr-branch-cleanup.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@
/** Branches that may never be deleted regardless of pull-request state. */
const PROTECTED_BRANCHES = Object.freeze(["main", "dev", "preview", "gh-pages"]);

/** Branch namespaces explicitly reserved for disposable pull-request work. */
const DISPOSABLE_BRANCH_PREFIXES = Object.freeze(["codex/", "ingw/"]);

/** Default grace period before a closed PR's head branch becomes eligible. */
const DEFAULT_GRACE_DAYS = 14;

function normalizeBranchName(value) {
return String(value || "").trim();
// Git permits non-ASCII whitespace in ref names, while String#trim removes
// it. Preserve API-provided branch identity byte-for-byte so two distinct
// refs cannot collapse into one deletion candidate.
return typeof value === "string" ? value : "";
}

/**
Expand Down Expand Up @@ -59,6 +65,7 @@ const KEEP_REASONS = Object.freeze({
CROSS_REPOSITORY: "cross-repository-head",
MISSING_CLOSED_AT: "missing-closed-at",
WITHIN_GRACE: "within-grace-period",
OUTSIDE_DISPOSABLE_NAMESPACE: "outside-disposable-namespace",
MOVED_SINCE_CLOSE: "branch-moved-since-close",
UNKNOWN_HEAD_SHA: "unknown-head-sha",
});
Expand All @@ -79,6 +86,11 @@ const KEEP_REASONS = Object.freeze({
* contributor's repository and this token has no business there.
* - A grace period after `closed_at` leaves room to reopen a PR that was
* closed by mistake.
* - Only branches under namespaces explicitly reserved for disposable pull-
* request work are eligible. Pull-request history alone must not authorize
* deletion of an unrelated persistent branch.
* - Branch names are compared and emitted byte-for-byte. Normalizing Unicode
* whitespace can merge distinct valid refs and delete the wrong branch.
* - The branch must still POINT AT a commit one of those closed pull requests
* had as its head. Matching by NAME alone deletes reused work: `codex/`-style
* names get picked up again all the time, and a branch recreated for new work
Expand Down Expand Up @@ -178,6 +190,11 @@ function planClosedPrBranchDeletions({
continue;
}

if (!DISPOSABLE_BRANCH_PREFIXES.some((prefix) => branch.startsWith(prefix))) {
keeps.push({ branch, reason: KEEP_REASONS.OUTSIDE_DISPOSABLE_NAMESPACE });
continue;
}

// The tip check, last because it is the most expensive claim to satisfy and
// the cheaper rules above have already excluded most branches.
//
Expand Down Expand Up @@ -219,6 +236,7 @@ function planClosedPrBranchDeletions({

module.exports = {
DEFAULT_GRACE_DAYS,
DISPOSABLE_BRANCH_PREFIXES,
KEEP_REASONS,
PROTECTED_BRANCHES,
isProtectedBranch,
Expand Down
58 changes: 56 additions & 2 deletions .github/scripts/closed-pr-branch-cleanup.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ const { describe, it } = require("node:test");
const assert = require("node:assert/strict");
const {
DEFAULT_GRACE_DAYS,
DISPOSABLE_BRANCH_PREFIXES,
KEEP_REASONS,
isProtectedBranch,
planClosedPrBranchDeletions,
} = require("./closed-pr-branch-cleanup.cjs");

const NOW = Date.parse("2026-08-26T00:00:00Z");
const DAY = 24 * 60 * 60 * 1000;
const HEAD_OID = "a".repeat(40);
const OTHER_OID = "b".repeat(40);
const NBSP = "\u00a0";
const longAgo = new Date(NOW - 60 * DAY).toISOString();

function closedPr(overrides) {
Expand All @@ -20,6 +24,7 @@ function closedPr(overrides) {
merged: false,
isCrossRepository: false,
headRefName: "codex/example",
headRefOid: HEAD_OID,
baseRefName: "dev",
closedAt: longAgo,
...overrides,
Expand All @@ -42,13 +47,17 @@ describe("isProtectedBranch", () => {
}
assert.equal(isProtectedBranch("codex/dev"), false);
});

it("declares the disposable pull-request branch namespaces", () => {
assert.deepEqual(DISPOSABLE_BRANCH_PREFIXES, ["codex/", "ingw/"]);
});
});

describe("planClosedPrBranchDeletions", () => {
it("deletes a branch whose only pull request closed unmerged past the grace period", () => {
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 42, headRefName: "codex/stale" })],
branches: ["codex/stale", "dev"],
branches: [{ name: "codex/stale", oid: HEAD_OID }, "dev"],
now: NOW,
});
assert.deepEqual(deletedBranches(result), ["codex/stale"]);
Expand Down Expand Up @@ -147,13 +156,58 @@ describe("planClosedPrBranchDeletions", () => {
it("ignores branches that no pull request ever used", () => {
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 80, headRefName: "codex/known" })],
branches: ["codex/known", "codex/never-a-pr"],
branches: [
{ name: "codex/known", oid: HEAD_OID },
{ name: "codex/never-a-pr", oid: HEAD_OID },
],
now: NOW,
});
assert.deepEqual(deletedBranches(result), ["codex/known"]);
assert.equal(keepReason(result, "codex/never-a-pr"), null);
});

it("keeps a persistent branch even when a closed pull request still matches its tip", () => {
const branch = "release/maintenance";
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 85, headRefName: branch })],
branches: [{ name: branch, oid: HEAD_OID }],
now: NOW,
});
assert.deepEqual(deletedBranches(result), []);
assert.equal(
keepReason(result, branch),
KEEP_REASONS.OUTSIDE_DISPOSABLE_NAMESPACE,
);
});

it("preserves Unicode whitespace so distinct valid refs never collapse", () => {
const disposable = `codex/live${NBSP}`;
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 86, headRefName: disposable })],
branches: [
{ name: "codex/live", oid: OTHER_OID },
{ name: disposable, oid: HEAD_OID },
],
now: NOW,
});
assert.deepEqual(result.deletions, [{ branch: disposable, pullRequests: [86] }]);
assert.equal(keepReason(result, "codex/live"), null);
});

it("does not trim leading Unicode whitespace into a disposable namespace", () => {
const branch = `${NBSP}codex/persistent`;
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 87, headRefName: branch })],
branches: [{ name: branch, oid: HEAD_OID }],
now: NOW,
});
assert.deepEqual(deletedBranches(result), []);
assert.equal(
keepReason(result, branch),
KEEP_REASONS.OUTSIDE_DISPOSABLE_NAMESPACE,
);
});

it("only plans deletions for branches that still exist", () => {
const result = planClosedPrBranchDeletions({
pullRequests: [closedPr({ number: 90, headRefName: "codex/already-gone" })],
Expand Down
54 changes: 47 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ it binds you regardless of which mechanism is within reach.
```bash
bun install
bun run typecheck # bun x tsc --noEmit (strict)
bun run test # full tests/ suite
bun run test:changed # import-graph tests against the resolved `dev` merge base
bun run test # full tests/ suite (PR-ready / explicit ask only)
bun run lint:gui # GUI eslint
bun run privacy:scan # credential/privacy scan used by CI
bun run build:gui # Vite GUI build
Expand All @@ -191,17 +192,55 @@ also if the hand-written pages name a command the registry does not have. That s
hypothetical: it caught a documented `ocx request-history` that never existed.

During implementation, use the smallest focused checks that directly cover the
changed subsystem. Do not run repository-wide `bun run typecheck` or
`bun run test` for a scoped change unless the change affects shared runtime,
routing, config, server behavior, a focused result is failed or ambiguous, or
the user explicitly asks for full validation.
changed subsystem. Prefer `bun test tests/<name>.test.ts` for a known file, or
`bun run test:changed` when the touch set is broader than one file. Do **not**
run repository-wide `bun run test` or a bare `bun test` with no file arguments
for a scoped change by default. `bun run test:changed` follows Bun's parsed module graph: it
selects test files that import changed modules, but it cannot see dependencies
expressed through subprocesses, source files read as data, or golden/derived
files. Run the relevant focused tests explicitly for those paths; if no reliable
focused set covers them, the full suite is required even for a scoped change.
That indirect-dependency case is the explicit exception to the scoped-change
default. The full suite is ~850 files, so otherwise reserve it for a failed or
ambiguous focused result, an explicit user request, or the PR-ready gate below.

Before creating or updating a non-trivial PR as review-ready, or before
approving such a PR, run `bun run typecheck` and `bun run test`. CI runs these
on Linux, Windows, and macOS.

Do not rerun passing checks on unchanged code merely for additional confidence.

## Minimal containers and agent sandboxes

Fresh dev containers and agent sandboxes (Cursor Cloud, devcontainers, CI
images) often ship Node but not Bun. Install it first:

```bash
curl -fsSL https://bun.sh/install | bash # installs ~/.bun/bin/bun
export PATH="$HOME/.bun/bin:$PATH"
bun install && (cd gui && bun install)
```

Run the proxy with `bun run src/cli/index.ts start --port <port>`. `/healthz`
reports status, `/` serves the dashboard, and the management API requires the
admin token the server writes to `$OPENCODEX_HOME/admin-api-token` at startup.

`bun run test` has five known environment-only failures in such containers.
They are not regressions; do not re-investigate them:

- `service diagnostics > status summary exposes the service log path`,
`CLI subcommand help > status prints diagnostics without starting the proxy`,
and `CLI subcommand help > invalid service and codex-shim usage include
remove alias` require a running systemd init; in a container PID 1 is
typically `tini` or another minimal init, so service commands report
"systemd not found".
- `package tree integrity > an in-place rewrite of the same byte length is
still a replacement` and `Codex Log Guard inspection > repeat inspection is
memoized and invalidated by a write` rely on filesystem mtime granularity
that some container filesystems do not provide.

Everything else passes (15480 pass / 16 skip / 5 fail as of 2.35.0).

## Issues and pull requests (agents)

Agent-created issues and PRs must use the repository templates. The gates
Expand Down Expand Up @@ -295,8 +334,9 @@ reviewers (Codex, CodeRabbit).
assumptions about a compile step, or code paths that break `bun run
typecheck` / `bun run test`.
- **Tests:** behavior changes in `src/` need a focused regression test near
the existing tests for that subsystem. Shared routing, adapter, config, or
server changes need the full suite green.
the existing tests for that subsystem. During implementation, run the relevant
focused files and use `bun run test:changed` for import-connected coverage as
described above; the full suite is the PR-ready gate.
- **Docs sync:** user-facing behavior changes should update `docs-site/` (and
keep translated locales from contradicting the English source).
- **Privacy:** `bun run privacy:scan` must stay green; never introduce logging
Expand Down
Binary file added assets/pr2950-capacity-expiry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions devlog/_fin/260828_kiro_turn_termination/021_audit_round3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# wp2 audit round 3 — the outer drain must not learn about completions

Reviewer: independent explorer lane, read-only, HEAD `a43e4cda`.
Verdict: **FAIL** on the plan as written in `020_wp2_duplicate_answer.md`.
This document records the finding and the corrected design.

## What the reviewer accepted

- Consuming the retained collection on a valid completion IS sufficient to remove
the user-visible duplicate. Required-mode text is held only in `deferred`
(`src/adapters/kiro.ts:1174-1179`, `:1207`), fallback text only in
`fallbackEvents` (`:1202-1205`), and a valid completion never live-flushes that
run because a completion alongside a real tool call is already a protocol error
(`:1260-1261`). With the commentary `text_delta` gone, `src/bridge.ts` never
splits the message.
- There is **no third emitter**. `:1523` builds a new event from
`completionAnswer`; it does not read `deferred`. The early-loop yields at
`:1398` and `:1418` only flush `deferred` when a real tool starts (`:1175`).
- Dropping `text_delta` while keeping non-text retained events loses nothing
load-bearing on this path. Tool events never sit in the collection — they splice
live and forbid a valid completion.
- `releaseEvent` is idempotent via its `eventBytes` map guard (`:808-810`), so a
double release cannot double-credit the budget.

## The blocker

`020`'s option 1 says to consume the collection at BOTH readers — the inner flush
and the outer drain in `parseKiroAttempt` (`:996-1001`). The reviewer showed the
second half is wrong:

> `996-1001` is also the leftover flush for early `terminal` returns that never
> hit `1468` (`1405-1413`). Dropping `text_delta` there without a completion flag
> hides the only commentary.

That outer drain is the release path for stream, protocol, and provider failures —
the row `020`'s own table requires to stay intact. A turn that fails after emitting
progress prose would lose that prose entirely, which is a worse defect than the
duplicate: the user would see an error with no indication of what the model had
been doing.

## Corrected design

The inner site is the only consumer, and it leaves the collection **empty**:

1. `src/adapters/kiro.ts:1468`, `mode === "required"`: when
`completionAnswer !== undefined`, splice the collection and consume it — drop
each `text_delta` after releasing its retention, yield every non-text event and
release it. When there is no completion answer, flush exactly as today.
2. `:1474`, `mode === "text_fallback"`: this branch is **already** gated on
`completionAnswer !== undefined`, so the same consume applies there and nowhere
else in that mode.
3. `:996-1001`, the outer drain: **unchanged**. Because step 1 splices, there is
nothing left for it to emit on the completion path, and it keeps its full
flush behaviour for every early-terminal path.

The correction is that suppression is expressed by emptying the collection at the
one site that knows a completion arrived — not by teaching a second,
failure-serving reader to discard text.

Untouched, per `020`'s release table: the `sawRealTool` flush (`:1483`), the
plain-text promotion (`:1490-1498`), and the empty/reasoning-only fallback
(`:1505`).

## Budget note

The reviewer's condition for a leak is "splice, skip `releaseEvent`, and skip
`releaseAll`". The consume path releases every event it drops, and
`releaseRetained`/`releaseAll` still runs for the `trackReplacement` remainder
(`:801-803`), which is not tracked in `eventBytes`. A test asserts the budget
returns to baseline.

Loading
Loading