Skip to content

fix(session): serialize archive publication and roll back failed saves - #1334

Merged
SaulMoro merged 1 commit into
Tencent:mainfrom
nateEc:fix/1325-session-publication
Oct 23, 2026
Merged

SaulMoro merged 1 commit into
Tencent:mainfrom
nateEc:fix/1325-session-publication

Conversation

@nateEc

@nateEc nateEc commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Overlapping session pushes could race after releasing the archive index lock, and a failed index write left an unindexed transcript/metadata pair. Serialize each archive-writing command through Git commit/pull/push, including migrate --push and session pull. Stage the pair, retain old files as renameable backups, and restore/remove it when the atomic index write fails.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature
  • Breaking change
  • Documentation only
  • Refactor / internal cleanup

Test Plan

  • npx tsc --noEmit passes

  • npm run lint passes

  • npx vitest run passes entirely (known baseline failures documented below)

  • Added/updated tests for the change

  • npm run build passes

  • npx vitest run src/__tests__/session-sync.test.ts src/__tests__/session-sync-pull.test.ts src/__tests__/session-cmd.test.ts: 135 passed.

  • Four rollback tests cover a new archive and an existing archive, with either the metadata rename or index rename failing. They preserve file bytes and retry without _1 duplicates. All four fail against clean main.

  • Real built CLI: session-publication.test.ts + session-archive-lock.test.ts through the E2E config: 3 passed. A bare remote's pre-receive gate holds the first push while a second command reaches the same archive; both exit 0, both sessions land remotely, and the checkout is clean. A Node filesystem fault makes the index rename fail, leaves no orphan pair, and the next push succeeds without a suffix. The existing JSON migration/copy/archive flow also passes. The two new E2E cases fail on main.

Broad validation: combined type check, lint, build, and 10 critical real-CLI E2E cases passed. The full five-branch unit run took 614.07s: 499 files; 12,244 passed, 36 skipped, 7 failed. Five failures came from the recall acceptance harness reading the old OpenCode argv position; a one-line test-only adapter fix preserves the attribution assertions. All 221 OpenCode/recall tests pass on the final combined snapshot after that correction; runtime source did not change.

The remaining two failures are independently reproduced on clean 6c57c3b7 with Apple Git 2.50.1: submodule-checkout.test.ts cannot read hook.teamai-post-checkout.command after preserving empty legacy Claude/Codex settings (24 passed, 2 failed on clean main). The full-suite checkbox stays unchecked for these baseline failures.

Local verification used macOS, Node 22.22.1, and local Git fixtures. GitLab/GitHub provider runs and additional live agent/Windows coverage were not run locally; remote CI cases may skip without credentials.

Related Issues

Related #1325: addresses concurrent publication and failed-save orphan files. The optional nested-wrapper/prototype-key cleanup is outside this fix.

Notes for Reviewers

Lock order is publication, then index. Network work holds the publication lock, never the short index lock; a contender waits up to ten seconds before an explicit busy/retry result. The lock is local to one machine; existing cross-machine merge/rebuild logic remains in use. Rollback failure keeps and names recovery backups. Much of the command diff is indentation of the existing publication bodies inside the lock. Bilingual user docs and the maintainer lock contract are updated.

- Hold a checkout-keyed publication lock across archive writes, commits, remote pushes, and pull/merge for session push, migrate --push, and session pull; keep the short index lock inside it.
- Stage transcript and metadata before replacing the pair, retain previous files as renameable backups, and restore or remove the pair when the atomic index write fails. Read/repair the index before installation so rebuild cannot adopt a partial save.
- Preserve recovery backups and report their paths if rollback itself fails; document local lock waiting, retry behavior, and the lock ordering for maintainers.
- Verify 135 focused tests plus real-CLI concurrent remote transactions, failed-index cleanup and retry without duplicates, and JSON migration/archive behavior; type checking, lint, and build pass.
@github-actions

Copy link
Copy Markdown

No confident findings in the specified PR diff.

The PR description satisfies the testing requirement: it records build, type-check, lint, targeted unit tests, and representative real built-CLI verification for concurrent publication and failed-save rollback. It also documents baseline failures and coverage deferred to CI.

Review was read-only; I did not run, build, or install anything from the PR.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This was generated by AI during triage.

@nateEc Checked 32b2b0a4. Concurrent publication and caught save failures behave as intended. The built-CLI run of session-publication.test.ts and session-archive-lock.test.ts passed all 3 tests. Additional probes confirmed dead-owner lock recovery, JSON success/busy output, and preservation of archive bytes after ordinary write and rename failures.

  • [P2 non-blocking] Recovery guidance after a partial rollback names backups already consumed by successful restores. In the injected metadata-restore failure, the transcript is restored and its backup no longer exists, but the error lists both backup paths. Track the backups that remain and name their live destinations. If both restores fail, the original bytes survive in backups, while the index still references missing live files.
  • [P2 non-blocking] The ten-second wait covers the entire network publication. With a slow post-receive hook, both commands succeed on base, while this head safely refuses the second as busy. The behavior is documented and retryable, so it does not block this fix.
  • [P3 nit] Reuse the existing withFileLock helper for publication to avoid another acquisition/release loop. Keep the index lock's ownership/re-entry handling.

Caught exceptions are covered by rollback; process death is outside that guarantee. With an existing valid index, a SIGKILL before index rename leaves an unindexed pair and a suffixed retry on both base and head. A later failed push can also reach the existing directory-wide Git-add fallback and publish retained recovery backups. That requires multiple failures and is optional hardening.

CI review reported no confident findings and accepted the testing record. We agree there is no blocking finding and the record is sufficient; the recovery guidance and contention behavior above are non-blocking qualifications. Current CI jobs passed. Local verification used macOS and local Git remotes; Windows and hosted-provider coverage were not rerun.

This PR owns session archive publication and rollback. #1280, and the branches stacked on it, own workflow publication and will need to resolve their overlapping session-command and dashboard-doc edits when they land.

@SaulMoro
SaulMoro merged commit 12b80e7 into Tencent:main Oct 23, 2026
18 checks passed
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.

3 participants