🤖 fix: replace_count double-replacement, devtools.jsonl rotation, tool_env SSH unset - #3763
🤖 fix: replace_count double-replacement, devtools.jsonl rotation, tool_env SSH unset#3763ibetitsmike wants to merge 1 commit into
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 384dc956b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
384dc95 to
bb237d4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb237d48ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
bb237d4 to
dd8e248
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd8e248dea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
dd8e248 to
9ed2e61
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ed2e61306
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
9ed2e61 to
bb9d398
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb9d3982cb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
bb9d398 to
061f154
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 061f154728
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
061f154 to
64b67af
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64b67af9a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
64b67af to
9cd5ca7
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9cd5ca7579
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
9cd5ca7 to
98db162
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98db16232a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
98db162 to
63e0f61
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6dced50ca2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
6dced50 to
f416b3e
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f416b3ebf1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
f416b3e to
1a56559
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a56559291
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1a56559 to
2cdcb88
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cdcb887d3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
2cdcb88 to
b6c950f
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6c950f364
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
b6c950f to
8aa83ea
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8aa83ea6e3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
8aa83ea to
ae33861
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae33861ad5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ae33861 to
d7693eb
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7693eba84
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
d7693eb to
170e1f3
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 170e1f34b6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…sonl rotation, tool_env SSH unset - file_edit_replace_string: with replace_count >= 2 and new_string containing old_string, matching resumed inside just-inserted text, duplicating site 1 and skipping site 2. Rebuild from pre-split parts instead. - devtools.jsonl: rotate to devtools.jsonl.1 past 50 MB inside the per-workspace write queue; clear() and removeWorkspaceData() clean the rotated file too. - .mux/tool_env: unset leaked SSH_CLIENT/SSH_CONNECTION/SSH2_CLIENT so Debian's bash.bashrc SSH heuristic cannot break make recipes under .SHELLFLAGS -eu.
170e1f3 to
5209d9a
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Three small deterministic fixes for verified mux behavior issues: a
file_edit_replace_stringdouble-replacement bug, unboundeddevtools.jsonlgrowth, andmakerecipe failures caused by SSH env vars leaking into the tool environment.Bottom of a 3-PR stack:
Fixes
file_edit_replace_stringdouble-replacement (file_edit_replace_shared.ts): the positivereplace_countloop restartedindexOffrom position 0 after each replacement, so whennew_stringcontainsold_stringas a substring, the second iteration matched inside the freshly inserted text (AoldB-oldC+old -> XoldY, count 2, producedAXXoldYYB-oldC). The loop now resumes searching after the inserted replacement. The-1(split/join) branch was already correct and is regression-guarded.devtools.jsonlunbounded growth (devToolsService.ts): API debug logs were appended with no size cap; only explicit clear or workspace removal reclaimed disk. Appends now rotate the file todevtools.jsonl.1(single rotated file retained, 50 MB cap insrc/constants/devtools.ts) inside the existing per-workspace write queue so rotation cannot race appends orclear(). Removal andclear()also delete the rotated file. Rotation compacts in-memory state into a self-contained rotated snapshot written atomically via tmp+rename and paired with the live file through generation markers (a crash between snapshot rename and live-file rewrite is detected on load via a log-retire sentinel appended to the live file before each snapshot commit, and repaired, so evicted runs cannot be resurrected; post-sentinel entries only count as live-format evidence when structurally complete (runs need id/workspaceId/startedAt, steps need id/runId/startedAt), so a malformed trailing line cannot make load discard a valid rotated snapshot as legacy; live-marker writes are atomic so a crash cannot leave an empty or partial live file). Clear commits an empty snapshot through the same protocol, so an interrupted clear stays cleared (active runs, including just-created zero-step runs, always retained; newest completed runs up to the cap; dropped runs evicted from memory and announced to open panels via a runs-evicted event), and loads replay the rotated file before the live file, so history survives restarts and repeated rotations cannot orphan step-updates from their base records.Eviction after a rotation is restricted to the run set frozen before the snapshot's filesystem I/O: runs created while the commit was in flight are never eviction candidates, and a frozen run that gained new steps mid-I/O is kept in memory with its base records re-appended to the fresh live file so those steps stay replayable. Workspace removal also deletes an abandoned snapshot temp file. A step create/update whose own append triggered the rotation that evicted its run suppresses its trailing emit so
runs-evictedstays the last event for that run. If the snapshot commits but the live-marker write fails (e.g. transient ENOSPC), the rotation still finishes reconciling memory with the committed retention decision (evicting dropped runs and notifying panels) before the failure propagates, and the next append repairs the marker before writing so post-failure entries survive the next restart. The same repair at load time is best-effort: a failing marker write no longer blocks reading the already-replayed snapshot, it just leaves the marker generation lagging so the next append retries the repair. clear() emits its cleared event once the empty snapshot commits, even when the marker write then fails, and a failed mid-rotation base-record re-append is queued as a pending repair that later appends must flush first, so a step can never be persisted ahead of its run's base records; the queue is only repopulated (and eviction only proceeds) while the frozen clear generation is current, so a clear landing during the failed repair cannot have pre-clear history flushed back after it. Repair retries are newline-sealed so a partially written attempt cannot merge with the retry into one malformed line, and generation validation also requires headroom for one safe increment so a corrupt MAX_SAFE_INTEGER cannot make the next rotation write an unloadable snapshot, and rotation restarts the sequence at 1 when the incremented generation could not pass that guard on the next load. Malformed generation metadata is ignored on load: values that would poison state (strings -> NaN) or that cannot be incremented (unsafe integers like 1e100, whose reuse would let a stale live marker pass as current and resurrect dropped runs) are rejected on both snapshot and live markers, and the live/snapshot pair requires exact generation equality: a marker that differs in either direction is stale (a higher one belongs to a snapshot no longer on disk), so the pair is recovered instead of replayed. A clear() landing while the snapshot I/O is in flight invalidates the frozen retention decisions entirely: reconciliation is skipped for that rotation, so a post-clear run recreation that reuses a frozen run ID is never evicted and the first post-clear step persists. Downgrade compatibility: a markerless live file without the matching retire sentinel means a pre-rotation binary rewrote it (its clear truncates only the live file), so the live file wins; a legacy clear stays cleared and legacy post-clear runs replay instead of the snapshot. Entries recorded after the last retire sentinel prove a legacy writer (only entries with a replayable payload count, so a torn payload-less line cannot discard the snapshot), so a downgraded session's post-crash runs also survive re-upgrade. All live-file appends go through a torn-tail seal: a rejected append arms a flag and the next append starts on a fresh line, so a partially written record or sentinel cannot destroy the record appended after it. Mutation events (run-created, step-created, step-updated) are emitted even when the persisting append rejects, since the entity can already be durable and memory backs detail lookups either way..mux/tool_envSSH unset:SSH_CLIENT/SSH_CONNECTION/SSH2_CLIENTleaking into the tool env makes Debian'sbash.bashrcSSH heuristic source an unguarded$PS1reference, which breaksmakerecipes under.SHELLFLAGS -euin non-interactive shells. The vars are now unset at the top oftool_env.Risks
Low. The replacement fix only changes the buggy overlap case (verified by new unit tests covering the overlap repro, non-overlap count=2, and
-1behavior). Rotation is bounded to the existing write queue; worst case is losing older debug log lines past 50 MB, which matches the cap's intent.Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh