Upgrade to Go 1.27 and repair the shell e2e sync harness - #208
Merged
Conversation
Crosses 1.26 and 1.27 in one step per docs/plans/active/go-1-27-upgrade.md. Full suite, e2e sync test, and serve smoke all green at 1.27.0.
All 16 scripts/e2e tests failed during shared setup, and had been failing on main since fb1327a (2026-06-19). That commit moved test/e2e/harness.go and scripts/e2e-sync-test.sh onto the device PKCE flow but missed scripts/e2e/harness.sh, which kept authenticating through /v1/auth/login/start + /auth/verify. Those endpoints now answer 410 Gone unless SYNC_LEGACY_DEVICE_AUTH is set, so every test died in setup. The failure was invisible: `curl -sf` under `set -e` exited the script with no assertion, no error text, and no diagnostic — which is why this read as a broken suite rather than a moved endpoint. Sync itself was never at fault. Migrate the harness to the same flow the real `td auth login` uses, matching the two already-migrated implementations rather than inventing a third shape: PKCE pair -> device/start -> read the magic link from the in-memory mailbox via the dev-only /internal/dev/last-email -> GET the approve URL -> device/poll. Supporting changes: - Provision users via `td-sync admin create-user` before the server opens the DB. device/start is non-enumerating, so an unprovisioned actor is silently never mailed a link. - Give the server SYNC_EMAIL_PROVIDER=memory, SYNC_DEV_EMAIL_INSPECT=1 and SYNC_EMAIL_BASE_URL so the magic link is readable, and lift the rate limits — the default auth limit is 10/min per IP, which a full suite trips. - Have setup() call start_server() instead of duplicating the launch block, so restart scenarios cannot drift from initial start. - Report why on every failure path instead of exiting silently. scripts/e2e/run-all.sh: 16 passed, 3 skipped (--full only), 0 failed. scripts/e2e-sync-test.sh passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ABM7FViu3tTWDEcGpm3qcY
Fill in the upgrade plan's acceptance section with what was actually run, and note that step 3 surfaced a pre-existing harness break rather than a 1.27 regression — no json/v2 attribution pass was needed because nothing failed for a JSON reason. Also flag for a future plan that the shell sync suite is neither part of `go test ./...` nor run by CI, which is why it rotted unnoticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ABM7FViu3tTWDEcGpm3qcY
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.
Lands the Go 1.27 toolchain upgrade, and fixes the failing sync e2e tests that surfaced while verifying it. The two are independent problems that happened to meet in the verification step, so they land together.
Go 1.27 upgrade
go.modmoves1.25.8→1.27.0, plus ago mod tidythat reclassifiedatotto/clipboardfrom indirect to direct. Both CI workflows resolve the toolchain fromgo-version-file: go.mod, so the directive is the only lever — no workflow change needed.The risk this plan called out was
encoding/json/v2becoming the default implementation, since td leans on JSON for sync payloads, the serve API, JSONL logs, and SQLite JSON columns. Nothing failed for a JSON reason, so the plannedGOEXPERIMENT=nojsonv2attribution pass was never needed.Note for downstream: Go 1.26 raised the floor for darwin builds to macOS 13+. Homebrew users are unaffected in practice.
The failing tests — root cause
All 16
scripts/e2etests failed, but not from 16 bugs. They all died in shared harness setup, and had been failing onmainsince 2026-06-19.Commit
fb1327amigratedtest/e2e/harness.goandscripts/e2e-sync-test.shto the device PKCE login flow. It missedscripts/e2e/harness.sh, which kept authenticating through/v1/auth/login/start+/auth/verify. Those endpoints now answer 410 Gone unlessSYNC_LEGACY_DEVICE_AUTHis set.The break was invisible:
curl -sfunderset -eexited the script with no assertion, no error text, and no diagnostic. That is why it read as a wholesale broken suite rather than a moved endpoint.Sync behavior itself was never at fault. This is test rot, not a sync regression.
The fix
The harness now drives the same flow the real
td auth loginuses, matching the two already-migrated implementations rather than inventing a third shape: PKCE pair →device/start→ read the magic link from the in-memory mailbox via the dev-only/internal/dev/last-email→ GET the approve URL →device/poll.Supporting changes:
td-sync admin create-userbefore the server opens the DB —device/startis non-enumerating, so an unprovisioned actor is silently never mailed a link.SYNC_EMAIL_PROVIDER=memory,SYNC_DEV_EMAIL_INSPECT=1,SYNC_EMAIL_BASE_URL, and lifted rate limits (the default auth limit is 10/min per IP, which a full suite trips).setup()now callsstart_server()instead of duplicating the launch block, so restart scenarios cannot drift from initial start.deploy/deploy.shalso calls the legacy endpoint, but deliberately: it asserts a 410/404 to confirm the secure-auth cutover took effect. Left alone.Verification
All at Go 1.27.0 (darwin/arm64):
go test -count=1 ./...with the release-safe env — clean, cache bustedscripts/e2e/run-all.sh— 16 passed, 3 skipped (--fullonly), 0 failed (was 0/16)scripts/e2e-sync-test.sh— greentd servesmoke —GET /health,GET /v1/issues,POST /v1/issuesround-trip; a malformed body returns a precise json/v2 validation errorgoreleaser release --snapshot --clean— all four targets build and archiveFollow-ups, deliberately not in this PR
go test ./.... That is exactly why it rotted unnoticed for two months, and it is the only coverage for real multi-client sync convergence. Wiring it in is worth doing, but it is slow (each script builds binaries and starts a server), so the CI-time tradeoff is a separate call.syncclient.LoginStart/LoginPoll(internal/syncclient/client.go:176-189) target the retired endpoints and have no non-test callers — the same rot, not yet swept.🤖 Generated with Claude Code
https://claude.ai/code/session_01ABM7FViu3tTWDEcGpm3qcY