fix(init): repair '(text)' config corruption and adjacent multi-agent push races (#739)#599
Merged
Merged
Conversation
…i-agent push fixes (#739)
The init walkthrough was writing the UI placeholder "(text)" into
hook-config.json for every ConfigType::String key, corrupting
tracker_remote (push fails with RemoteMisconfigured('(text)')) and
both sentinel.*.model keys on every new repo. populate_tracker_remote
treated the corrupt value as a manual edit and refused to repair it,
so even `crosslink init --force` left the config broken.
While tracing the resulting "new repo never works" symptom, three
adjacent multi-agent bugs surfaced that share the same blast radius
(fresh-remote bootstrap not propagating). All four are fixed together
so a freshly-inited repo actually round-trips between agents.
* config_registry.rs (build_config): add `ConfigType::String => continue`
so the UI placeholder is never persisted; apply_tui_choices now
preserves the template defaults.
* init/mod.rs (populate_tracker_remote): treat "(text)" as a corrupt
template default to be auto-repaired alongside the literal "origin",
so existing affected repos self-heal on `crosslink init --force`.
Collapsed the new arm with the absent-key case (clippy).
* sync/mod.rs (read_tracker_remote): defensive guard — when the
on-disk value is "(text)", warn once with remediation instructions
and fall back to "origin", so unpatched repos see actionable text
instead of a generic push failure.
* sync/cache.rs (push_hub_if_ahead) + commands/locks_cmd.rs (sync_cmd):
sync now pushes the hub branch when local is ahead of remote OR the
remote branch doesn't exist. Previously, pushes only happened lazily
via SharedWriter::commit_*, leaving a race window where agent B's
init_cache saw an empty remote (because A's bootstrap had never
been pushed), created its own orphan branch, and then couldn't
rebase onto A's eventual push.
* sync/mod.rs (classify_push_failure): expanded NonFastForward
substring set to cover "! [remote rejected]", "cannot lock ref",
"incorrect old value provided", and "failed to push some refs" —
the four shapes git emits for concurrent-ref-update races.
Pre-fix, parallel lock claims both fell through to Other, both
call sites returned LocalOnly, and both agents printed "Claimed
lock on issue #1".
* commands/cpitd.rs: add `#[serde(default, alias = "total_groups")]`
to CpitdOutput::total_pairs to tolerate the cpitd 0.3.0 schema
rename. Full schema migration for non-empty clone_reports is
separate.
Tests added (8):
- walkthrough_core_tests::build_config_never_emits_string_typed_keys_with_text_placeholder
- 3 × test_populate_tracker_remote_repairs_text_placeholder_{with_detected, without_remotes, to_non_origin}
- test_read_tracker_remote_falls_back_when_corrupt_placeholder
- classify_push_failure_concurrent_ref_lock_is_non_ff
- classify_push_failure_remote_rejected_variant
- 4 previously-failing smoke tests now pass: cpitd_scan_dry_run,
parallel_lock_claim_one_winner, two_agents_create_issues,
adversarial_same_issue_write_conflict_convergence
Full suite green: 1883 lib + 2859 bin + 194 integ + 159 smoke pass,
0 failures, fmt clean, no new clippy errors introduced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five related fixes that together make a freshly-
crosslink init'd repo actually round-trip between agents.(text)placeholder corruption —WalkthroughCore::build_configno longer writes the UI placeholder"(text)"intohook-config.jsonfor everyConfigType::Stringkey. Pre-fix, every new repo gottracker_remote = "(text)"(and matching corruption for bothsentinel.*.modelkeys), causing every push to fail asRemoteMisconfigured("(text)").populate_tracker_remoteandread_tracker_remotenow auto-repair / gracefully degrade on the corrupt value.sync_cmdnow calls a newSyncManager::push_hub_if_ahead()at the end. Previously, pushes happened lazily only viaSharedWriter::commit_*, so agent B'sinit_cachecould run between A's bootstrap sync and A's first write, see an empty remote, create its own orphan branch, and then never rebase onto A's eventual push.PushFailure::NonFastForwardclassifier — added"! [remote rejected]","cannot lock ref","incorrect old value provided", and"failed to push some refs"to the substring set. Pre-fix, concurrent lock claims hit a path where both agents gotPushFailure::Other, both call sites returnedLocalOnly, and both printed "Claimed lock on issue Support hook-config.local.json for machine-local configuration #1".total_pairs→total_groupsvia#[serde(default, alias = "...")]. Empty-outputcpitd scanno longer fails to parse. Non-empty schema migration is a separate concern.Test plan
config_registry, 3 ininit, 1 insync::testsfor the read fallback, 2 insync::testsfor the classifier patterns, plus the test counts come frombuild_config+populate_tracker_remote+ classifier expansions)smoke::cli_tooling::test_cpitd_scan_dry_runsmoke::concurrency::test_parallel_lock_claim_one_winnersmoke::coordination::test_two_agents_create_issuessmoke::coordination::test_adversarial_same_issue_write_conflict_convergencecargo fmt --checkcleancargo clippy --all-targets -- -D warningsintroduces zero new errors (21 pre-existing errors on develop, 21 on this branch)Recovery for existing affected repos
Repos initialized before this fix carry the corrupt config sticky —
init --forcefrom this build will auto-repair them. For pre-build repos, run:Out of scope (follow-up)
push_hub_if_aheadhappy path / offline path / divergence path. The smoke tests exercise the behavior end-to-end.🤖 Generated with Claude Code