Skip to content

fix(config,im): Save aborts on real read errors and surfaces external-section failures + ctx-aware key retry (#4697, #4698) - #4732

Merged
topcheer merged 6 commits into
mainfrom
fix-4697-4698
Oct 31, 2026
Merged

topcheer merged 6 commits into
mainfrom
fix-4697-4698

Conversation

@topcheer

Copy link
Copy Markdown
Owner

Summary

Tests

  • TestIssue4697_ReadErrorAbortsSave (EISDIR path swap → Save must error, not full-write), ..._ExternalSectionFailureSurfaces (poisoned vendors.yaml → error mentioning external sections; baseline save still green).
  • TestIssue4698_KeyRetryCtxAware (source pin: select ctx.Done/time.After inside the bounded loop).

Verification

  • GOFLAGS="-p=1" GOMEMLIMIT=2GiB GOGC=50 go build -tags goolm ./internal/config/ ./internal/im/ green
  • Full packages green: internal/config (13.7s), internal/im (50.3s)

Junjun Zhang and others added 5 commits October 31, 2026 16:05
…-section failures + ctx-aware key retry (#4697, #4698)

#4697 (A, high): Save() treated ANY read error as first-write and fell
into the full-overwrite branch - deep-merge and tombstone protection
were skipped, disk-only fields (externally written keys, unknown
fields) were silently wiped with a success report. Read errors other
than file-missing now abort (#1160 semantics, matching
patchConfigFile/patchExternalFile).
#4697 (B, medium): saveExternalSections discarded its three Save*
errors - vendors/im/mcp write failures (read-only dir, full disk, lock)
returned Save()=nil, the UI reported success, and the config reverted
on restart. The first failure now propagates.

#4698: the #4644 key-retry sleep was a raw time.Sleep inside the serial
syncer OnEvent callback - up to 2.4s per bad event, stalling every room
behind it, and unbounded by ctx cancellation. The wait is now a select
over ctx.Done/time.After; retry semantics preserved.

Co-Authored-By: ggcode <noreply@ggcode.dev>
…inistically writes vendors.yaml

CI root-caused: SaveVendors strips default vendors and skips the write
when nothing remains (len(raw)==0) - the empty-set baseline made the
poison step environment-dependent. A non-default vendor guarantees the
file exists before poisoning.

Co-Authored-By: ggcode <noreply@ggcode.dev>
…walk

The walk was the wrong oracle - resolve the path exactly as Save does
(exported helper on Dir(cfg.FilePath)) and assert presence by stat.

Co-Authored-By: ggcode <noreply@ggcode.dev>
…iring pin

The Save-level baseline oracle depended on which sections a save chooses
to persist (default-strip can legitimately skip vendors.yaml) - three CI
rounds of environment-chasing ended by testing the propagation directly:
saveExternalSections against a read-only dir, plus a source pin for the
Save() wiring.

Co-Authored-By: ggcode <noreply@ggcode.dev>
…ilure

Permission-mode oracles proved non-portable (0500 dir still writable in
some sandboxes; default-strip legitimately skips empty sections). A
directory at the vendors.yaml path fails AtomicWriteFile's final rename
deterministically on every Unix - and the unit call removes all
Save-level environment dependence.

Co-Authored-By: ggcode <noreply@ggcode.dev>
@topcheer

Copy link
Copy Markdown
Owner Author

CI blocked by pre-existing main failure (not this PR's diff): the Test job's only failure is internal/tui TestHandleTunnelInboundMsg_WithImages panic (nil refusalLedger via ReleaseMatchingRefusals, refusal_ledger.go:761) — main's own latest run fails the same test (bca7ae7 #4726 suspect). This PR's config/im tests are green in the same run. Filed #4736 for the main-side crash. Per the CI hard rule this PR stays unmerged until main is fixed and the branch re-runs green; the fix commits themselves are verified locally (config+im full packages green, 3x targeted).

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.

1 participant