Skip to content

chore(sync-service): upgrade Elixir to 1.20.0#3992

Open
alco wants to merge 6 commits into
mainfrom
alco/upgrade-elixir-otp
Open

chore(sync-service): upgrade Elixir to 1.20.0#3992
alco wants to merge 6 commits into
mainfrom
alco/upgrade-elixir-otp

Conversation

@alco
Copy link
Copy Markdown
Member

@alco alco commented Mar 10, 2026

Summary

  • Upgrade Erlang/OTP from 28.4 to 29.0
  • Upgrade Elixir from 1.19.5 to 1.20.0
  • Fix all Elixir 1.20 compatibility warnings in Electric source code:
    • Remove unused require statements
    • Add pin operator (^) in bitstring size patterns
    • Rename Changes.record() to Changes.row() (new built-in type conflict)
    • Remove unreachable code flagged by the type checker
    • Use apply/3 in telemetry.ex to avoid compile-time conditional type warning
  • Bump postgrex 0.21.1 -> 0.22.2 (+ transitive deps db_connection, telemetry)

@alco alco added the claude label Mar 10, 2026
@claude
Copy link
Copy Markdown

claude Bot commented Mar 10, 2026

Claude Code Review

Summary

Upgrades Erlang/OTP from 28.4 to 29.0 and Elixir from 1.19.5 to 1.20.0-rc.5, with fixes for new compiler/type-checker warnings (pin operator in bitstring patterns, record() built-in conflict, unused requires, dead code) and a Postgrex bump. The Elixir-side changes are mostly mechanical and look correct; the main risk areas are the pre-release Elixir version and the switch of the Lux dependency to a moving develop branch.

What's Working Well

  • Cleanly scoped: warning fixes are minimal and don't drift into refactoring.
  • Cache-key invalidation across all six workflow files now includes hashFiles('.tool-versions'), so stale _build/deps from the old OTP/Elixir won't be restored after this change lands — exactly the right call.
  • Changeset is present and correctly lists all three publishable Elixir packages (@core/sync-service, @core/electric-telemetry, @core/elixir-client).
  • The record()row() rename in Electric.Replication.Changes is the right fix for the new built-in type conflict, and all three call sites in the same module are updated consistently.
  • Pin operators (^) added in all four affected bitstring patterns (pure_file_storage.ex ×3, ecto_adapter/postgres.ex ×1).

Issues Found

Important (Should Fix)

1. Shipping on an Elixir release candidate

File: .tool-versions:2, packages/sync-service/Dockerfile:1

The PR pins production to elixir 1.20.0-rc.5-otp-29 / ELIXIR_VERSION=1.20.0-rc.5. RC builds can have regressions that don't show up in unit tests (compiler edge cases, OTP interop bugs, performance regressions). For a sync service that runs long-lived GenServers under load, this is a non-trivial risk.

Suggestion: Either wait for 1.20.0 final before merging, or explicitly document in the PR description that this is being shipped on an RC intentionally (and to which environments — dev only? staging? prod?). Worth flagging which hexpm/elixir Docker tag is being used and whether it's a published image.

2. Lux dependency now tracks a moving branch

File: integration-tests/Makefile:4-6

The integration-test harness now clones https://github.com/hawk/lux.git and git checkout develop. develop is a moving branch — future runs of make (and CI cache misses) can produce different Lux builds for the same Electric commit, making bisects and reproductions painful.

The cache key in integration_tests.yml is lux-develop-${otp-version}, so once the cache populates it's stable for that runner, but a fresh runner / cache eviction will pull HEAD of develop at that moment.

Suggestion: Pin to a specific tag or commit SHA, e.g. git checkout <sha> (and include the SHA in the cache key). If develop is required because no tagged release supports OTP 29 yet, leave a TODO comment with a link to the upstream issue/PR tracking the next release.

3. apply/3 workaround in telemetry.ex has no explaining comment

File: packages/sync-service/lib/electric/telemetry.ex:26

if apply(Electric, :telemetry_enabled?, []) and telemetry_code_available? do

Electric.telemetry_enabled?/0 returns a literal boolean compiled from a module attribute (@telemetry_enabled? in lib/electric.ex:154), and 1.20's type checker can presumably constant-fold the conjunction into a dead branch. Using apply/3 opaques the call to defeat that — fine, but it looks like an accidental anti-pattern at first glance.

Suggestion: One-line comment, e.g. # apply/3 prevents 1.20's type checker from constant-folding this into a dead branch.

Suggestions (Nice to Have)

4. Behavior change tucked into a "dead code" removal

File: packages/sync-service/lib/electric/shapes/consumer.ex:835-838

defp handle_apply_event_result(state, {:error, reason}) do
  state = handle_event_error(state, reason)
  {:noreply, state, {:continue, :stop_and_clean}}
end

Old behavior: when state.terminating? was false, the function returned {:stop, ...} directly via stop_and_clean/1 — the consumer stopped synchronously. New behavior: always defer to handle_continue(:stop_and_clean, ...), which then stops.

This is correct given all three handle_event_error/2 clauses end in mark_for_removal/1 (so terminating? is always true afterward), and the continuation-then-stop path converges to the same outcome. But the change is presented in the PR description as "remove unreachable code flagged by the type checker," which understates it slightly — anyone tracing exit ordering will see one less direct {:stop, ...} return path. Worth a sentence in the PR body, or an inline comment explaining why both branches collapse.

5. Changeset wording

File: .changeset/upgrade-elixir-rc.md

The changeset is correctly marked patch (no public-API change), but the filename upgrade-elixir-rc.md will read oddly in changelogs if you ship the final 1.20.0 later. Consider upgrade-elixir-1-20.md or similar to avoid the "rc" label leaking into release notes.

Issue Conformance

No linked issue. For an OTP/Elixir version bump this is acceptable, but the PR description should make explicit what motivated the upgrade now (waiting on a Postgrex fix? new Elixir feature in use? CI compatibility?). Two of the three CI failures reported by the Blacksmith bot in PR comments (CallHomeReporterTest, ShapeCacheTest snapshot-timeout) may or may not be flakes — worth confirming green before merge given this PR changes the runtime substrate.

Previous Review Status

The previous Claude review on this PR was a test placeholder (Testing unicode-escaped JSON) with no substantive content, so there is nothing to track as addressed.


Review iteration: 2 | 2026-05-18

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
4059 1 4058 52
View the top 2 failed test(s) by shortest run time
Elixir.Electric.Shapes.FilterTest::test optimisations where clause in the form `array_field @> const_array` is optimised
Stack Traces | 0.0483s run time
8) test optimisations where clause in the form `array_field @> const_array` is optimised (Electric.Shapes.FilterTest)
     .../electric/shapes/filter_test.exs:817
     Assertion with < failed
     code:  assert add_reductions < max_reductions
     left:  7724
     right: 6500
     stacktrace:
       (elixir 1.20.0-rc.5) lib/enum.ex:983: Enum."-each/2-lists^foreach/1-0-"/2
       .../electric/shapes/filter_test.exs:845: (test)
Elixir.Electric.ClientTest::test response handling client is resilient to server errors
Stack Traces | 0.0667s run time
33) test response handling client is resilient to server errors (Electric.ClientTest)
     test/electric/client_test.exs:796
     ** (Electric.Client.Error) Unable to retrieve data
     code: ] = Client.stream(client, ctx.shape, []) |> Enum.take(4)
     stacktrace:
       (electric_client 0.10.2) .../electric/client/stream.ex:232: Electric.Client.Stream.handle_error/2
       (electric_client 0.10.2) .../electric/client/stream.ex:295: Enumerable.Electric.Client.Stream.reduce/3
       (elixir 1.20.0-rc.5) lib/enum.ex:3705: Enum.take/2
       test/electric/client_test.exs:917: (test)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@blacksmith-sh

This comment has been minimized.

@alco alco mentioned this pull request Mar 12, 2026
@alco alco force-pushed the alco/upgrade-elixir-otp branch from fe1f308 to 504092c Compare March 13, 2026 14:19
@blacksmith-sh

This comment has been minimized.

@alco alco marked this pull request as ready for review March 13, 2026 21:49
@alco alco self-assigned this Mar 13, 2026
@alco alco force-pushed the alco/upgrade-elixir-otp branch from 40ebf7e to e9dbd76 Compare May 18, 2026 14:38
@alco alco changed the title chore(sync-service): upgrade Elixir to 1.20.0-rc.3 chore(sync-service): upgrade Elixir to 1.20.0 May 18, 2026
@alco alco force-pushed the alco/upgrade-elixir-otp branch 2 times, most recently from ade153c to 2afe4fe Compare May 18, 2026 15:05
alco added 6 commits May 19, 2026 01:30
Upgrade Elixir from 1.19.5 to 1.20.0. Fix Elixir 1.20 compatibility warnings:

- Remove 7 unused `require` statements detected by stricter checks
- Add pin operator (`^`) in bitstring size patterns (5 occurrences)
- Rename `Changes.record()` type to `Changes.row()` to avoid
  overriding new built-in `record/0` type
- Remove unreachable `:shutdown` case clause in pool.ex (caught by
  improved type inference from preceding handle_info clause)
- Use `apply/3` in telemetry.ex to avoid type checker flagging
  compile-time conditional as always-false
@alco alco force-pushed the alco/upgrade-elixir-otp branch from 2afe4fe to 6906f5b Compare May 18, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant