code-yeongyu / code-yeongyu/lazycodex
subagent-limit-guard still unconditionally rewrites [features.multi_agent_v2] max_concurrent_threads_per_session = 1000 (v4.17.0) — v2 follow-up to #97
- Dominant language
- TypeScript
- Stars
- 3.5k
- Forks
- 216
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
`ensureSubagentConcurrencyLimit()` ends by calling `ensureMultiAgentV2ThreadLimit(result)` **unconditionally**, and that helper writes `[features.multi_agent_v2] max_concurrent_threads_per_session = 1000` into the user's `config.toml` on every run — creating the section if absent, and overwriting any user-set value if present. This happens regardless of the selected model, and there is no equivalent of the "hand-removed key stays removed" behavior that the same file deliberately implements for the legacy `agents.max_threads` path.
Pinned source at `v4.17.0` (f39306f):
- Constant `CODEX_SUBAGENT_THREAD_LIMIT = "1000"`: [subagent-limit-guard.mjs#L5](https://github.com/code-yeongyu/lazycodex/blob/f39306f1adab6ff155fd736cc7376d27156472bc/plugins/omo/scripts/migrate-codex-config/subagent-limit-guard.mjs#L5)
- Unconditional call at the end of `ensureSubagentConcurrencyLimit`: [subagent-limit-guard.mjs#L38](https://github.com/code-yeongyu/lazycodex/blob/f39306f1adab6ff155fd736cc7376d27156472bc/plugins/omo/scripts/migrate-codex-config/subagent-limit-guard.mjs#L38)
- Writer that inserts/overwrites the key: [subagent-limit-guard.mjs#L81-L90](https://github.com/code-yeongyu/lazycodex/blob/f39306f1adab6ff155fd736cc7376d27156472bc/plugins/omo/scripts/migrate-codex-config/subagent-limit-guard.mjs#L81-L90)
## Why it matters
On multi_agent_v2 sessions (e.g. `gpt-5.6-sol`), `max_concurrent_threads_per_session` is effectively the **only concurrency gate that holds**:
- `agents.max_depth` is not enforced on the v2 spawn path (openai/codex#32027, still open)
- the v2 non-root thread limit is derived from this cap (`cap − 1`)
- in our testing on codex-cli 0.144.1, `[features.multi_agent_v2] enabled = false` did not disable v2 for `gpt-5.6-sol` — model catalog metadata took precedence
We audited a runaway session on codex-cli 0.144.1 + `gpt-5.6-sol` where the injected runtime hint `"There are 1000 available concurrency slots"` appeared 23× in the rollout, alongside a spawn tree of 1,004 descendant threads (depth 11) and ~46 GB RSS before manual intervention. After we restored Codex's stock default (cap 4) by deleting the override, source inspection showed this migration would silently re-write `1000` on the next session start with the plugin enabled — i.e. a user who fixes their config gets re-broken.
## What v4.17.0 already improved (appreciated)
- v2-preferring models now get `agents.max_threads` **removed** instead of written (the `v2Preferred` branch in `ensureSubagentConcurrencyLimit`), and a hand-removed key stays removed on the no-model-evidence path
- the #118 stale `hide_spawn_agent_metadata` cleanup works as described
The v2 cap write is the remaining piece of #97.
## Suggested fix
Any of, in order of preference:
1. Stop writing `max_concurrent_threads_per_session` unless the user opts in
2. Honor existing / hand-removed user values, mirroring the `raiseExistingAgentsMaxThreads` philosophy already applied to the v1 key
3. At minimum, make the limit configurable instead of hard-coding `"1000"`
`LAZYCODEX_CONFIG_MIGRATION_DISABLED=1` exists but is all-or-nothing — enabling it also loses the genuinely useful guards (e.g. the #118 cleanup).
## Environment
- codex-cli 0.144.1, model `gpt-5.6-sol` (multi_agent_v2), macOS
- Originally observed with OMO 4.15.1; behavior confirmed by source inspection at `v4.17.0` (f39306f)
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with plugins/omo/scripts/migrate-codex-config/subagent-limit-guard.mjs at ensureSubagentConcurrencyLimit and ensureMultiAgentV2ThreadLimit, using the pinned v4.17.0 source. Trace the existing v1 hand-removed behavior and verify the migration across absent, existing, and removed max_concurrent_threads_per_session values; done means the guard no longer unconditionally overwrites user configuration.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100