Skip to content

fix(core,storage): unify the Codex thread source gate - #3702

Merged
Astro-Han merged 2 commits into
apache:mainfrom
cat0825:fix/3693-codex-source-authority
Aug 30, 2026
Merged

fix(core,storage): unify the Codex thread source gate#3702
Astro-Han merged 2 commits into
apache:mainfrom
cat0825:fix/3693-codex-source-authority

Conversation

@cat0825

@cat0825 cat0825 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #3693.

The foreign-session scanner (@maka/core/foreign-session) and the Codex Session adapter (@maka/storage) each owned a private set of eligible Codex source tokens. The sets drifted, so the same Codex thread could be visible through one surface and invisible through the other:

source on the thread scanner (before) adapter (before)
exec dropped listed
atlas, chatgpt (bare) listed dropped
{"custom":"cli"}, {"custom":"vscode"} listed dropped
NULL / absent column dropped listed

The last two rows are divergences beyond the table in the issue. The NULL case is the one that bites hardest in practice: older Codex schemas have no source column at all, so the scanner hid every legacy thread that the adapter happily catalogued.

CODEX_SUPPORTED_THREAD_SOURCES in @maka/core/foreign-session is now the single authority, and the adapter's duplicate isRootCodexSource is deleted.

On the union. The issue asks for a ruling on whether bare exec belongs. It does: #2502's own description lists "root cli, exec, and vscode Sessions" as what the adapter surfaces, and exec has been in the adapter's set since its first commit (23f624b). The scanner's set predates it (#1057, #1208) and simply never learned about headless codex exec runs. The unified set is therefore cli, exec, vscode, atlas, chatgpt — the union, adopting exec rather than dropping it.

Two supporting changes fall out of the merge:

  • codexSourceToken now also accepts an already-parsed object, which is the shape rollout session_meta payloads arrive in. Previously the adapter needed its own recursion to handle that; now one function covers the bare token, the JSON object string, and the parsed object.
  • isSupportedCodexThreadSource states the absent-is-eligible rule once, instead of having each call site re-derive it (the scanner spelled it row.source !== undefined && token === undefined, the adapter spelled it as an early return true, and only one of the two also handled null).

Internal subagent threads ({"subagent":{"thread_spawn":{…}}}) resolve to no token in either form and stay out of both surfaces, unchanged.

Verification

  • npm --workspace @maka/core run test — 658 pass, 0 fail (clean + build + node --test)
  • npm --workspace @maka/storage run test — 927 pass, 14 skipped, 0 fail
  • npm exec -- biome check on the four changed files — no fixes applied

New coverage:

  • packages/core/src/__tests__/foreign-session.test.ts — bare exec; already-parsed objects ({custom:'atlas'}atlas, {custom:'unknown'} → undefined, subagent → undefined); a table-driven check that every token in the set resolves in both the bare and {"custom":…} forms; NULL source column treated as absent rather than unsupported.
  • packages/storage/src/__tests__/codex-session-adapter.test.ts — a catalog listing seeded with all five sources in both forms plus a subagent thread, asserting the adapter now lists exactly what the scanner accepts and still excludes the subagent.

The storage test was checked against a negative control: reverting only codex-session-adapter.ts and rebuilding fails it with wrapped cli was dropped, so it exercises the merged gate rather than passing vacuously.

@cat0825

cat0825 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han when you have a moment, would you mind taking a look at this one?

Status: CI is green, and GitHub reports it as mergeable against current main with no conflicts. The change is small (+112 / -40 across 4 files) and scoped to unifying the Codex thread source gate between core and storage, with test coverage on both sides.

No rush — flagging it since it is review-ready and I would rather not let it drift into conflict. Happy to rebase or split it if that makes review easier.

@M4n5ter
M4n5ter force-pushed the fix/3693-codex-source-authority branch 2 times, most recently from 6a3c67b to 87ea505 Compare August 26, 2026 10:02
@github-actions github-actions Bot added the effort/M Under 500 readable lines label Aug 27, 2026

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for consolidating the Codex source parsing and removing the duplicate adapter predicate. A shared gate is the right direction, and the adapter-level tests describe the intended token set clearly.

The remaining issue is at the composition boundary. I left one inline P1 because the primary SQLite scanner still applies its old SQL allowlist before the shared gate can run, so the normal product path does not receive several newly supported sources. The rollout fallback also does not read or check payload.source, meaning unsupported and subagent rollouts can still pass when SQLite is unavailable.

In other words, the shared helper is correct, but it has not yet become the authority for both scanner paths. The PR is also currently conflicting with main; after resolving the scanner boundary, it will need a rebase and fresh exact-head CI.

Reviewed with Codex and an independent @Reviewer agent. I verified the exact head, adapter and store call paths, SQLite prefilter, rollout fallback, test coverage, and current merge state.

中文对照

谢谢你收敛 Codex source 解析并删除 adapter 中重复的 predicate。建立共享 gate 是正确方向,adapter 层测试也清楚描述了预期支持的 token。

剩余问题位于真正的 composition boundary。我留了一条 P1:主要的 SQLite scanner 仍然会在共享 gate 执行前使用旧 SQL allowlist 过滤数据,因此正常产品路径仍然无法看到多个新支持的 source。没有 SQLite 时使用的 rollout fallback 也没有读取和检查 payload.source,所以 unsupported 或 subagent rollout 仍可能进入列表。

也就是说,共享 helper 本身是正确的,但它还没有真正成为两条 scanner 路径的 authority。这个 PR 当前也与 main 冲突;修复 scanner 边界后,还需要 rebase 并重新跑精确 head 的 CI。

本次审查使用了 Codex 和一个独立的 @Reviewer 子代理;我核对了精确 head、adapter 与 store 调用路径、SQLite 预过滤、rollout fallback、测试覆盖和当前合并状态。

Comment thread packages/core/src/foreign-session.ts
The foreign-session scanner and the Codex Session adapter each owned a
private set of eligible `source` tokens, so the same Codex thread could be
visible through one surface and invisible through the other:

- bare `exec` was accepted by the adapter but dropped by the scanner;
- bare `atlas`/`chatgpt` and wrapped `{"custom":"cli"}` / `{"custom":"vscode"}`
  were accepted by the scanner but dropped by the adapter;
- a NULL `source` column was admitted by the adapter but dropped by the
  scanner, hiding threads written by older Codex schemas.

Make `CODEX_SUPPORTED_THREAD_SOURCES` in `@maka/core/foreign-session` the
single authority (`cli`, `exec`, `vscode`, `atlas`, `chatgpt`) and delete the
adapter's duplicate gate. `codexSourceToken` now also accepts an
already-parsed object, which is the shape rollout `session_meta` payloads
arrive in, and the new `isSupportedCodexThreadSource` states the
absent-is-eligible rule once instead of at each call site. Internal
subagent threads (`{"subagent":{…}}`) still resolve to no token and stay
out of both surfaces.

Closes apache#3693
@cat0825
cat0825 force-pushed the fix/3693-codex-source-authority branch from 87ea505 to 05f4056 Compare August 29, 2026 16:26
@cat0825

cat0825 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (now at 05f4056) and the branch is conflict-free again — mergeable: MERGEABLE.

Conflicts and how they were resolved:

  • packages/core/src/foreign-session.ts — auto-merged, but I verified the result by hand rather than trusting it, because both sides edited this region: upstream fix(core): bound foreign session timestamps #3835 added FOREIGN_SESSION_MAX_EPOCH_MS / isValidForeignEpochMs() right below FOREIGN_SESSION_MIN_EPOCH_MS, and fix(core): strip the deprecated Cf bidi-adjacent controls #3846 widened FOREIGN_UNSAFE_CHARS to \u2066-\u206F. Both are intact and unmodified; my change is still only the CODEX_SUPPORTED_THREAD_SOURCES / codexSourceToken() / isSupportedCodexThreadSource() block plus the single call swap in normalizeCodexThreadRow().
  • packages/core/src/__tests__/foreign-session.test.ts — the real conflict. Git refuses to text-merge this file (warning: Cannot merge binary files) because a sanitizeForeignText fixture embeds a literal NUL byte, so it fails the binary heuristic. I resolved it as a genuine 3-way merge instead of picking a side: substituted a private-use sentinel for the NUL, ran git merge-file (0 conflicts — the two hunks are disjoint), then restored the exact byte. Result keeps upstream's new U+206A-206F cases in both sanitizeForeignText and isSafeForeignId, and my #3693 source-gate cases; 647 lines = 586 + 618 − 557, the exact union with nothing dropped.
  • packages/storage/src/codex-session-adapter.ts and its test applied cleanly; upstream had not touched them.

Net diff is unchanged at +112/-40 across the same 4 files, and nothing under apps/desktop/** or packages/cli/** is touched.

Verified locally on the exact pushed head (this repo is npm workspaces + tsc + node --test):

  • npm --workspace @maka/core run typecheck → clean
  • npm --workspace @maka/storage run typecheck → clean
  • npm --workspace @maka/core run testtests 710 / pass 710 / fail 0, including accepts a bare exec source, matching the Codex adapter (#3693), accepts a NULL source column as absent rather than unsupported, and the new isSupportedCodexThreadSource suite
  • npm --workspace @maka/storage run testtests 990 / pass 983 / fail 0 / skipped 7; all 7 skips are the pre-existing Windows/NTFS-gated cases that no-op on macOS. The adapter file itself is pass 10 / fail 0, including lists every thread source the foreign-session scanner accepts (#3693)
  • npx biome lint + biome format on the 4 changed files → no findings; node scripts/asf-license-headers.mjs check → clean

On your inline P1: still open, and deliberately not fixed in this push. You are right on both counts — CODEX_SOURCE_SQL_VALUES in packages/storage/src/foreign-session-store.ts:545 is still ['cli', 'vscode', '{"custom":"atlas"}', '{"custom":"chatgpt"}'], so the SQL prefilter drops bare exec, bare atlas/chatgpt, and wrapped cli/vscode before normalizeCodexThreadRow() ever sees them; and the rollout fallback around line 315 reads codexRolloutSessionMeta(record) without consulting payload.source at all. That fix lands in a 5th file that is outside this PR's stated scope, and it needs a call from you on direction before I write it: derive the SQL values from CODEX_SUPPORTED_THREAD_SOURCES (cheap, but the bare/wrapped cross-product plus source IS NULL makes the IN (…) list somewhat wide), or drop the source predicate from SQL entirely and let the shared gate be the only authority (simplest and genuinely single-authority, at the cost of filtering after LIMIT — which is exactly the ordering hazard the existing comment on that block warns about). Happy to extend this PR with the store-level test you asked for, or to send it as a follow-up — whichever you prefer.

@Astro-Han no rush at all, but since the rebase you asked for is done and CI is running on the exact head, this is ready for another look whenever it suits you.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for consolidating the Codex thread source gate into the shared core/storage boundary. The normal SQLite-backed path now uses one source policy across scanning and import, which fixes the real drift without creating a second catalog authority.

I found no P0 or P1 issues on exact head dd591d148b7d7a4a64c24d8503a7630a998840c1. One non-blocking P2 remains in the recovery fallback: when SQLite is unavailable, rollout JSONL metadata currently drops the source field before the shared gate, so unsupported/subagent sessions may still appear in the picker and be importable. This requires the fallback path, a user selection, and remains reversible, so it should be fixed but does not block the normal-path correction. Approving.

AI-assisted review: Codex helped inspect the source-policy authority, SQLite and JSONL paths, import reachability, tests, and exact-head CI. I verified the severity and final approval decision.

中文对照

谢谢把 Codex thread 的 source gate 收敛到共享的 core/storage 边界。正常 SQLite 路径现在让扫描和导入共用一套 source policy,修复了真实漂移,也没有建立第二套 catalog 权威。

精确 head 上没有 P0/P1。恢复 fallback 中还有一个不阻塞的 P2:SQLite 不可用时,rollout JSONL metadata 会在共享 gate 前丢掉 source 字段,因此 unsupported/subagent session 仍可能出现在选择器中并被导入。它需要进入 fallback、再由用户主动选择,而且可逆,所以应该修,但不阻塞正常路径的修正。批准。

本次评审使用 Codex 辅助检查 source policy 权威、SQLite/JSONL 路径、导入可达性、测试和精确 head CI;分级和最终批准决定由我确认。

@Astro-Han
Astro-Han merged commit 0c43678 into apache:main Aug 30, 2026
2 checks passed
saltand pushed a commit to saltand/maka-agent that referenced this pull request Aug 31, 2026
Use one shared source policy for Codex thread discovery and import across the core and SQLite-backed paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex thread source eligibility differs between the foreign-session scanner and the Codex session adapter

2 participants