Skip to content

feat(offsets) astubbs#255: an opaque rider in the commit-metadata payload, under one envelope magic byte - #460

Draft
astubbs wants to merge 14 commits into
masterfrom
feats/offset-metadata-rider
Draft

feat(offsets) astubbs#255: an opaque rider in the commit-metadata payload, under one envelope magic byte#460
astubbs wants to merge 14 commits into
masterfrom
feats/offset-metadata-rider

Conversation

@astubbs

@astubbs astubbs commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Part of #255 - the core half of KTD-S7. It does not close the issue: the Kafka Streams consumer of this slot lands as a rung on feats/ks-streams-reconciled above #454, and the idle-but-punctuating gap the issue also names needs its own tracking issue (see Known Residuals).

Description

Parallel Consumer's commit metadata gains one generalised slot: an opaque rider an embedder can hand PC per partition, carried in the same OffsetAndMetadata string as the offset-hole map, under one new envelope magic byte 'X' ([u16 len][rider][inner encoding or nothing]). PC never reads the bytes; it needs the length and nothing else.

What binds it, in the order a reviewer will want to check them:

  • Old payloads are byte-identical. No rider means no envelope. OffsetRiderCodecTest.aPayloadWithNoRiderIsWhatPcWritesToday pins this across every inner encoding.
  • The rider is charged against the metadata cap, never the back-pressure threshold. A rider is a floor back-pressure cannot relieve; charged there it would deadlock a caught-up partition. OffsetRiderOverheadTest measures the cost over the whole domain and pins that the back-pressure engagement point does not move.
  • Configuring a rider never costs a partition metadata it would otherwise have committed. The budget ladder sheds the rider, then the dropped-rider marker, then the envelope, and only then strips the hole map as it always has (RiderBudgetRung, PartitionStateRiderBudgetTest).
  • The supplier is guarded like retryDelayProvider. A throw, a null, or an over-cap rider is counted, rate-limit logged, and the commit still happens (RiderSupplierGuardTest).
  • Read-back is one public entry point - OffsetMapCodecManager.decodeRider(committedOffset, metadata, policy) - through the codec's single decode choke point, with fix(offsets)! astubbs#197: the metadata policy governs every unreadable payload, and defaults to IGNORE #207's unreadable-metadata policy governing an envelope this build cannot read (OffsetRiderReadBackTest, ForeignOffsetMetadataOnAssignmentTest).
  • Compatibility is a hard precondition, stated and announced. Every released PC throws on an unknown magic byte from inside the rebalance callback, before any policy is consulted; the metadata is durable, so one such member crash-loops. OffsetRiderUpgradeDowngradeTest reproduces that against a broker and pins the recovery path; the option's javadoc and docs/features/offset-metadata-rider.yaml carry the rule, and PC logs it once at INFO when a supplier is configured.

Design record: docs/solutions/architecture-patterns/one-owner-per-metadata-field-with-an-opaque-rider.md (migrated to master with this PR, citations repaired). Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md - its settled decisions (one envelope byte, not one per encoding; core slot ships alone off master) are the ones a simplify pass would otherwise reverse.

Magic byte registry note for #306: 'X' is taken by this envelope, and EncodedOffsetPair now has three switches that need an arm per encoding, not two.

Release decision for the author: the read side (understanding 'X') and the write side (riderSupplier) ship together here, both targeted at 0.6.0.0 alongside #207's policy. docs/features/offset-metadata-rider.yaml records the alternative - read side in 0.6.0.0, write side one minor later so every 0.6.0.0 reader predates every writer - under release_assumptions. Say which you want before merge.

Known Residuals

From the pre-PR review (run 20260907-013655-cce2ea5f, three validated findings applied in 6e85d5768) and the first review round on this PR (Codex and Claude; seven of eight threads fixed in 6725ad93a, master merged in 703591778). None of these is an unapplied actionable finding; they are the design items routed to a human, plus what the mutation lane found:

  • Design call (Codex thread, left open): on the no-encoding fallback - the map cannot be encoded at all, so the payload is stripped - the supplier is never called and no rider is written. The ladder's own strip rung has the same shape. Carrying a rider-only envelope on both strip paths would match the caught-up commit and is the direction I lean, but it changes the ladder's documented last rung and the pc.offsets.rider.dropped accounting the budget tests pin.

  • The ladder's first rung is unreachable by construction (PIT sweep of PartitionState, no coverage on shedRiderForSize): the write-time allowance in riderFromSupplier already guarantees a rider that honours it fits the RIDER rung, so fitRiderToBudget can only descend to the marker when the statics move between the supplier call and the fit. Either the allowance or the rung is redundant; which one to keep is the same decision as the point above.

  • dups: similarity is red on this PR and cannot be made green here: the engine scores the new rider test classes against each other and against WorkClaimStateMachineTest at 80-90% by whole-file likeness, and it has no allowlist (docs/inflight/ci-dup-similarity-cannot-accept-known-duplication.md). The route the repo has used before is an override at merge.

  • Design call (adversarial, advisory): a same-build group member with no supplier configured overwrites a partition's rider with "none" on its next commit. The docs say every member needs the policy; they do not yet say every member needs the supplier. Decide for the Streams rung whether PC should write the DROPPED marker when it read a rider it will not re-supply, or whether "every member configures the supplier" is the documented rule.

  • Design call (adversarial, advisory): a supplier that throws on the shutdown commit loses the rider a restart reads, silently (counted, rate-limit logged). Same question: NONE vs DROPPED on supplier failure beside an offset map.

  • Documented, not enforced (reliability, adversarial): the supplier runs on an engine thread - the broker-poll thread under consumer commit modes, the control thread holding the produce write lock under the transactional producer - with no timeout. A blocking supplier stalls producers; a slow one counts against the rebalance timeout. Same contract as retryDelayProvider; the Streams rung must publish its rider from a lock-free snapshot.

  • Test gaps worth a follow-up: no test drives the rider through PC's own close-time commit and a restart read-back (the integration test overwrites that commit); no Error-subtype through the supplier guard; no decodeRider against a Kafka-Streams-shaped inner payload by name.

  • Tracking issue needed: a caught-up, non-dirty partition never refreshes its rider, because the supplier runs only on commits the partition would have made anyway (KTD8 / R12). Kafka Streams: give a Streams topology PC's per-key parallelism #255's idle-but-punctuating case is therefore still open after this PR.

Post-Deploy Monitoring & Validation

  • Metrics to watch (all new, parallel-consumer-core PCMetricsDef): pc.offsets.rider.size (distribution - should track the embedder's payload), pc.offsets.rider.dropped (must stay at zero in steady state; a non-zero rate means the rider does not fit beside the hole map), pc.offsets.payload.stripped (should not rise after enabling a rider - if it does, the rider is being blamed for a cap the hole map already hit), pc.offsets.rider.supplier.failed (any increase is a bug in the embedder's supplier: it is the only continuous signal, because the warning is rate limited).
  • Log lines: the one-time INFO at startup naming the reader-version precondition (announceRiderCompatibilityRequirement); WARNs mentioning riderSupplier (once per 30 s per PC instance: a throw, or an over-cap rider - the message says whether the marker was written or nothing was).
  • Healthy signal: no riderSupplier WARNs; pc.offsets.rider.dropped flat; pc.offsets.payload.stripped unchanged from before the rider was configured; the existing back-pressure ratio metrics unmoved.
  • Failure signal / rollback trigger: any group member logging UnknownOffsetMetadataMagicException or CorruptOffsetMetadataException from onPartitionsAssigned, or crash-looping on assignment - a member predates this build. Do NOT roll the writer back: unsetting riderSupplier does not heal the group (a caught-up partition never recommits). Follow the recovery in docs/features/offset-metadata-rider.yaml boundaries (kafka-consumer-groups --reset-offsets --to-current).
  • Validation window and owner: first rolling restart after enabling a supplier, plus one full rebalance; the embedder team owning the supplier.

Without a supplier configured this PR changes no wire byte, so a deployment that does not set riderSupplier needs no additional operational monitoring.

Checklist

  • Docs updated - docs/features/offset-metadata-rider.yaml, docs/features/invalid-offset-metadata-policy.yaml, CONCEPTS.md (Rider), README.adoc metrics (regenerated from src/docs/README_TEMPLATE.adoc), docs/building.md (Truth-generator nested-type hazard), docs/refactoring.md, the migrated design record
  • User-facing feature documentation data added under docs/features/ - offset-metadata-rider.yaml
  • Tests added/updated - envelope, codec, budget ladder, supplier guard, read-back, metrics, overhead table, foreign-metadata arms, and a broker upgrade/downgrade integration test
  • docs/inflight/ working note (pr-/branch-) started at the PR's first commit - none needed: the two touched notes (pr-207-offset-encoding-policy.md, bug-no-metric-for-discarded-offset-metadata.md) were narrowed to what stays open; the branch's cross-branch state is the plan and the settled KTDs in it, and gh shows the rest
  • Title & body reflect the final content of this PR
  • Ran ce-simplify and ce-code-review locally - both; review run 20260907-013655-cce2ea5f (status complete, verdict "ready with fixes"), fixes in 6e85d5768

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ

astubbs and others added 11 commits September 6, 2026 21:49
Kafka Streams on PC cannot restore its stream time after a restart against a group PC committed,
because PC owns the commit metadata field and Streams' own TopicPartitionMetadata is never written
there (KTD-S7 of the spike plan). The settled direction is an opaque rider: one generalised
extension slot in PC's payload that an embedder fills per partition at commit and reads back on
assignment, which PC carries without interpreting.

This plan covers the core half only - the envelope encoding, the budget ladder, the supplier hook,
the read-back entry point, metrics and the record - as a PR off master; the Streams consumer is a
later rung on the Kafka Streams stack. It records the decisions research settled: the rider is
charged against the metadata cap and never against back-pressure (charged against the threshold a
rider is a floor back-pressure cannot relieve, and on a caught-up partition a permanent block); the
ladder sheds the rider, then the drop marker, before the hole map; every released PC crash-loops on
an unknown magic byte, so configuring a rider is opt-in with a stated minimum reader version and a
recovery procedure; and magic byte 'X' lands ahead of the density work in #306, with the
three EncodedOffsetPair switches named as the collision surface.

Two things stay open on purpose: whether the rider's write side ships in 0.6.0.0 or the minor after
(the graceful-degradation fix it relies on is unreleased), and a tracking issue for the
idle-but-punctuating commit gap that blocks the customer's end-to-end result.

CONCEPTS.md gains the Rider entry the plan's vocabulary needs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…he commit metadata payload

An embedder that runs on Parallel Consumer needs somewhere to persist a few bytes per partition
across a restart, and the only slot a consumer-group commit offers is the metadata string PC already
owns for its frontier encoding. This adds the envelope that will carry such a rider: magic byte 'X',
an unsigned 16-bit length, the rider bytes, then today's hole encoding unchanged - or nothing, for a
caught-up partition. PC never reads the rider; it knows only the length.

The type is a standalone codec with no enum constant and no engine wiring, so the format can be
reviewed on its own before anything consumes it. Three rules are enforced at the boundary rather
than documented: the length is validated against the bytes remaining before anything is allocated,
an envelope never nests (an inner first byte of 'X' is corrupt on both read and write), and a
zero-length rider is not a value - it is the marker the budget ladder writes when it sheds a rider
for size, so a reader can tell "dropped" from "never configured". Every array crosses the boundary
as a copy.

Two things the plan did not anticipate: the read side declares the checked
CorruptOffsetMetadataException, matching EncodedOffsetPair's own decode signature, so the caller
that wires it in must route it through the unreadable-metadata policy; and a getter on a rider that
is not present throws rather than returning an empty array, because an embedder decoding zero bytes
as a real value is exactly the hazard the dropped marker exists to prevent.

Test-first: the test compiled red against the missing type, then went green with every layout
claim asserted byte for byte, including the boundary lengths, the signed-negative length reading,
truncation inside the header, and copy defence in both directions.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…ld payloads are byte-identical

Wires the rider envelope into PC's offset-metadata codec so a payload can carry an embedder's blob
beside the frontier's hole encoding. A new OffsetEncoding constant, RiderEnvelope, takes magic byte
'X' at the tail of the list, after the Kafka Streams pair, so the density work in #306 - which
inserts its own constants after RunLengthV2Compressed - conflicts on a known line rather than a
surprising one.

The decode side unwraps the envelope before an EncodedOffsetPair is built and re-enters the same
method on the remainder, so the inner magic byte is resolved through maybeDecode and reaches the
unreadable-metadata policy exactly as an unknown outer byte does. A rider-only payload answers the
same highest-seen offset as an empty payload: one below the committed offset, which is what keeps
the committed record from being marked as already done. A malformed envelope - a length past the
bytes remaining, a nested envelope, a truncated inner body - is routed through the policy under
both IGNORE and FAIL, and the buffer-slicing exceptions are caught as a backstop behind the length
validation, so nothing escapes the rebalance callback as a bare runtime exception. All three
switches over the encoding gain an arm, including decodeBody's, whose default was the escape hatch.

For the caller, one value carries what the codec decoded: DecodedMetadata, the existing
HighestOffsetAndIncompletes plus a Rider state. Every public decode overload keeps its signature and
return type and projects the rider away; the rider travels on a package-private family underneath.
A payload the policy discards reports the rider as unreadable, so "never configured" and "thrown
away" never read the same. On the write side the codec gains two entry points the budget ladder in
PartitionState will use: one runs the encoder competition exactly once and returns the inner bytes
(or the empty body for a caught-up partition, keyed on the same condition tryToEncodeOffsets returns
early on), the other assembles inner bytes plus a rider into the string. With no rider the string is
byte-identical to today's, asserted per codec.

Test-first: the new codec test and the malformed-envelope arms compiled red against the missing
shapes, then went green with the wire-format set untouched apart from one enum-source exclusion.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U8.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…e commit metadata, guarded like the retry-delay provider

An embedder can now give Parallel Consumer a rider per partition: ParallelConsumerOptions gains
riderSupplier, a function from a RiderContext - the partition, the offset the commit is paired
with, and the most bytes the supplier may return - to the bytes to carry. The context's offset is
the one rule the confluentinc#893 family settled: a payload travels with the offset it describes,
so the supplier is told which one that is rather than left to guess.

The supplier is user code on the engine's commit thread - the broker poller under the consumer
commit modes, the control thread under the transactional producer, and the shutdown commit - so it
is guarded the way the retry-delay provider and the meter registry already are: a throw or a null
is caught, logged once through a rate limiter, and treated as no rider for that call; a rider
longer than its cap is dropped with its own rate-limited warning. Failing the commit instead would
turn a persistent supplier fault into a partition that never commits, which is worse than a missing
rider. The supplier is called exactly once per encode, after the hole encoding is produced and
inside the same snapshot, so a second read of partition state never enters the payload.

The cap is derived, not literal: the headroom between the back-pressure threshold and the metadata
cap, converted from Base64 characters to raw bytes and bounded by the envelope's own length field -
so a rider at its cap can only fail the cap check once the hole map has already crossed the
threshold. At the defaults that is 768 bytes. A caught-up partition writes a rider-only envelope
and stays unblocked; one whose rider is over the cap writes no metadata at all.

Configuring a rider is opt-in, and the option's javadoc and a one-time INFO line at startup say
why the whole group must first run a PC that carries the unreadable-metadata policy: every released
PC fails assignment on an unknown magic byte, the payload is durable, and the recovery is a
consumer-group offset reset.

Test-first: the guard test compiled red, then failed on the missing call site, then went green -
including the poll-thread arm over a real processor and MockConsumer, and the transactional arm
proving the committer calls the supplier on its own caller's thread with the transaction lock held.
One thing the tests could not assert is byte identity against a separately built no-rider payload:
the encoder competition breaks ties between equal-sized encodings by set iteration order, so the
same state encodes as either of two magic bytes across calls. The tests assert the deterministic
half and record the measurement.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
… against back-pressure, and the ladder never costs the hole map

Back-pressure exists so a partition's metadata can shrink as work completes, and rider bytes do
not shrink. Charged against the 0.75 threshold a rider is a floor the mechanism cannot relieve, and
on a caught-up partition a permanent block, because nothing is in flight to complete. So
updateBlockFromEncodingResult now takes two lengths: the hole encoding's own character length is
judged against the threshold, and the assembled string's against the cap. With no rider configured
the first number is the assembled string's own length, byte for byte, which is why the existing
back-pressure test's block point does not move - pinned over a corpus of hole densities by
recomputing the old rule independently.

When the assembled payload would exceed the cap, a ladder chooses what to shed, predicted from the
Base64 closed form so the outer codec runs once per commit on the winning rung: the rider, then the
three-byte drop marker, then the envelope altogether - because a hole map that sits within the
marker's cost of the cap must still commit, and configuring a rider may never cost a partition
metadata it would otherwise have written. Only after all three does today's strip fire. A rider the
supplier's own cap already accepted provably fits alongside its offset map, so in practice the
descent starts from the marker; the rung chooser is tested on its own boundary table regardless.

The rung enum lives in its own public file only because the Truth subject generator recurses into
every type in the state package and cannot see a package-private one.

Also fixes a vacuous pair of assertions in the previous unit's guard test: comparing a decoded byte
to the magic-byte constant boxed one side to Integer and the other to Byte, so the inequality could
never fail. Both sides are ints now.

Test-first: two scenarios went red on today's behaviour - a rider over the threshold blocked the
partition, and a hole map within the marker's cost of the cap was stripped outright - then green
with the whole core unit suite.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
… citations repaired and its stale claims corrected

The design record for the rider - one owner per metadata field, the other side riding inside it as
an opaque blob - lived only on the Kafka Streams branches, invisible to every master-side search,
while the code that implements it was landing here. It now lives beside that code. Every file:line
citation became a path plus a greppable anchor, the io.confluent paths became bz.stub ones, and
three claims were corrected in place rather than copied: the unreadable-metadata policy defaults to
IGNORE since #207, not FAIL; PC's magic bytes include ByteArrayCompressed's 0xEE, which is
not a printable letter, so the "always a letter" argument for Kafka Streams' graceful degradation
is scoped to the bytes PC can actually emit; and the patched StreamTask method the record credited
never existed under that name. A dated section records what shipped: the envelope, the budget
rule, the supplier option and its guard, the read-back seam with its explicit policy, and the
compatibility rule with its recovery procedure.

docs/features gains the rider option as a planned entry - target release per the open question of
whether the write side ships with the decoder or one minor later - carrying the minimum reader
version, the recovery procedure and its cost, and the operator-facing change that metadata on a
caught-up partition no longer means holes in flight. The policy's entry names how an envelope this
build cannot read is treated. The pr-207 note records that this is the first encoding the policy was
shipped ahead of.

Two refactoring lines record what the work found: the encoder competition breaks ties between
equal-sized encodings by set iteration order, and serialiseIncompleteOffsetMapToBase64 now has no
main-code caller. A third records that the file-ref gate's history-pointer grammar stops at the
first slash, so every git-show citation of a feats/ branch here needs a marker instead of the
escape the citation guide prescribes.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U7.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…etadata string, and an old reader degrades the way the policy designed

An embedder that does not own PC's consumer - Kafka Streams on PC runs the engine over a stub
consumer and reads committed metadata through its own - needs a way to get its rider back from the
string it holds. decodeRider takes the committed offset, the string and the unreadable-metadata
policy, delegates to the codec's single decode choke point so the outer string codec is inherited,
and returns the rider's state: none for a payload with no envelope, dropped for the marker the
budget ladder writes, present with a copy of the bytes, unreadable when the policy discarded the
payload. Under FAIL the policy's typed exception propagates. The policy is a required parameter and
a test forbids ever adding an overload without one: the runtime default is IGNORE while the
policy-less base64 helper deliberately picks FAIL, so any default here would decide on the caller's
behalf between two answers that disagree.

The compatibility claim is now proven against a broker rather than argued from code. Two records
stuck failing keep holes open while everything around them completes, so the periodic committer
writes holes and a rider in one payload - the shape a crash leaves - and the test captures it from
the broker. A plain consumer then rewrites the payload's leading byte to one no encoding claims,
which is how a reader without the envelope constant is simulated on one classpath, and the two arms
are each other's control: under IGNORE a new instance resumes from the committed offset and loses no
record; under FAIL assignment dies with the typed exception. An abandoned instance was rejected as
the crash shape on a mechanism, not taste: it keeps heartbeating, so the coordinator refuses the
rewrite from a non-member.

Test-first: the read-back test compiled red against the missing method, and two mutations of the
implementation - ignoring the caller's policy, returning none unconditionally - were killed before
the tests were trusted.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…ing supplier, and keep the two payload ratios honest

The rider is best-effort by contract, so the only way an operator can tell it is working is a
metric. Four per-partition meters join the offset-encoder subsystem: the size of each rider written,
a counter for a rider shed to fit the metadata limit (over its own cap, or squeezed out by the
offset map), a counter for a payload stripped because the offset map itself did not fit, and a
counter for a riderSupplier that threw - the one signal that a rider-based feature has silently
stopped working, since a throw is treated as no rider and the commit goes ahead. A null return is
deliberately not counted: the option's contract makes it a legitimate "no rider this commit", and
counting it would make a healthy embedder look broken. All four are registered and removed with
the partition through the guarded path the meter-registry incident established.

The two existing ratios were answering the same question once a rider was in the string. The
density ratio now records the hole encoding's own length, so it keeps measuring how well the
offsets encode; the headroom ratio records the assembled string, rider included, because that is
the number that says how close a commit is to the cap. A caught-up commit records neither: its
offset range is negative or zero, and Micrometer keeps a zero sample rather than dropping it, so
leaving the guard to the library would drag both ratios toward zero on every steady-state commit.

The one double-count in the design was guard-then-ladder: a rider over its own cap is counted when
the supplier's guard turns it into the drop marker, and the ladder can then shed that marker for
size. The shed counts only a present rider, so one lost rider is one count - pinned by an assertion
a deliberate mutation of that branch turned red.

The metrics table in the README is regenerated from the definitions, never hand-edited; the
inflight note on discarded metadata records which half this closes and that a payload discarded on
read is still only a warning.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
… whole domain, and pin that back-pressure does not move

The forever-format rule here is measure first: a claim about where a payload crosses a size
boundary is checked exhaustively through the real pipeline, not sampled on friendly inputs. This
test does that for the rider. For every rider length the supplier can be offered, across a spread
of inner-encoding lengths, the assembled string's length equals the Base64 closed form the budget
ladder predicts from - through the real assemble path, envelope included. Then, for each incumbent
encoding over uniform, clustered and all-incomplete hole maps, it searches for the exact hole count
at which back-pressure engages and at which the cap engages, with and without a rider, driving real
partition-state commits at each boundary and its predecessor. A fixed ladder of ranges would have
missed the band where the rider is what crosses the cap: it is a handful of bytes wide out of
three thousand.

The shape of the result: the back-pressure engagement point does not move at all - reverting the
threshold to the pre-change rule turned the test red at the first encoding - and the cap engagement
point moves earlier by exactly the rider's Base64 footprint, for every combination that reaches the
cap. The free competition ships an encoding of the same size with the same compression decision
either way; the test asserts size rather than magic byte because equal-sized ties resolve by set
order.

Deliberately not excluded from the mutation lane, against the plan: every assertion is an exact
value, so the class kills mutants the lane would otherwise count as survivors, and it costs seconds.

The design record gains the measured-cost note in place of KTD-S7's budget caveat, and the build
guide records a hazard this test found: a nested type inside a test class makes the Truth subject
generator emit an import that breaks the next build.

Plan: docs/plans/2026-09-05-001-feat-offset-metadata-rider-plan.md, U6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…and let the rider tests share one fixture

Eight workers built this branch one unit at a time, and a simplification pass over the whole
found what that leaves behind. The rider parameter threaded through EncodedOffsetPair's decode
method was only ever handed "none": the behaviour it was reserved for - an intact envelope keeping
its rider when the inner offset map is corrupt - had been solved a level up, where the envelope
decoder substitutes the outer rider into the result. The parameter and the branch it fed are gone;
the test that proves the behaviour is unchanged.

The five new test classes had each restated the same fixture - the Base64 closed form, seeded
riders, the remember-and-restore dance around the two mutable statics, a partition state with
random holes, decode-and-unwrap of a committed payload. They now share one test-support class,
which still restates the arithmetic independently of production so no test agrees with itself.
The two hand-rolled logback captures use the LogCapture utility the module already has, and a
local byte-array concat gives way to Guava's. The rider javadoc says "offset map", the term the
surrounding code has always used, instead of "hole map".

One reviewer suggestion was measured and reverted: sharing one module across the overhead test's
search probes moved its wall-clock within noise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…adoc names, and the over-cap warning says what was written

Three findings from the pre-PR code review (run 20260907-013655-cce2ea5f), all
validated by an independent pass, plus two mutants the PIT lane reported surviving.

decodeRider promised CorruptOffsetMetadataException and UnknownOffsetMetadataMagicException
under FAIL in its javadoc, but declared only OffsetDecodingError. Both are checked; they
reach the caller sneaky-thrown through the same policy handler every other entry point in
this family uses. A checked type that is thrown but not declared cannot be caught by name -
javac rejects the catch as unreachable - so an embedder could not write the handling the
javadoc described. The only test used assertThatThrownBy, whose callable is declared
throws Throwable, which is why the compiler never saw the mismatch. The signature now
declares both, and a new test is the caller the javadoc describes, written with real catch
blocks so it compiles only while the declaration holds.

The over-cap rider warning described one outcome where riderFromSupplier produces two.
Beside an offset map the rider becomes the dropped marker and a reader sees DROPPED; on a
caught-up partition there is no map for the marker to sit beside, no metadata is written
at all, and a reader sees NONE. The warning promised the marker in both cases, sending an
operator looking for a state the caught-up commit never writes. It now branches on which
happened, and the caught-up guard test asserts the text. The feature record's boundary
line made the mirror-image error - it said an over-cap rider is "treated as no rider",
contradicting its own values section that reserves the marker - and now states both.

PIT (offsets lane, PR-scoped against origin/master, 301 of 301 mutants scored): the
envelope class itself scored 23/23; two survivors in the rider's own code are closed here -
describeEnvelope's unknown-inner-encoding branch had nothing telling it apart from the map
branch, and Rider.hashCode was pinned only as "equal riders hash equal", which any constant
satisfies. The remaining offsets survivors are in code this branch did not write.

Not changed on review advice, deliberately: the reviewer's proposal to split PartitionState
at a 1000-line bar (the plan places the ladder there; RiderBudgetRung is split out only for
the Truth-generator constraint), and the proposal to hide encodeOffsetsToInnerBytes and
assembleMetadataPayload (public because PartitionState calls them cross-package, the same
shape as the pre-existing makeOffsetMetadataPayload).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
@astubbs

astubbs commented Sep 7, 2026

Copy link
Copy Markdown
Owner Author

@claude review this

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Claude finished @astubbs's task in 2m 53s —— View job


Review: opaque rider envelope (#255)

  • Gather context (branch commits, PR body, prior notes)
  • Read core new/changed files (OffsetRiderEnvelope, OffsetMapCodecManager, EncodedOffsetPair, PartitionState, RiderBudgetRung, RiderContext, ParallelConsumerOptions, PCModule, AbstractParallelEoSStreamProcessor)
  • Review test coverage for the claims in the PR body
  • Check the "Known Residuals" section against the code
  • Post review feedback

Note on scope: the review workflow allowlist is resolved from master's copy of .github/workflows/claude.yml, not this PR's — any tool/permission grants this PR itself adds are not in effect for me. I read the code from the checked-out PR branch (6e85d5768), so everything below is evidence about this PR, not about my own setup.

Overall

This is a well-reasoned, carefully documented change. The envelope format (OffsetRiderEnvelope), the single decode choke point (EncodedOffsetPair.decodeToRiderAndIncompletes / decodeEnvelope), and the budget ladder (RiderBudgetRung + PartitionState.fitRiderToBudget) all check out against the invariants the PR body claims:

  • Old payloads are byte-identical. assembleMetadataPayload takes the no-envelope path unconditionally on RiderState.NONE, matching OffsetRiderCodecTest's pin.
  • Rider charged against the cap, not back pressure. updateBlockFromEncodingResult measures innerEncodingCharacterLength (offset map alone) against the back-pressure threshold and assembledPayloadLength (rider included) against the hard limit — confirmed by reading PartitionState.java:159-216.
  • Budget math is internally consistent. PartitionState.maxRiderBytes derives the byte allowance from the same base64Characters/base64CapacityInBytes closed forms RiderBudgetRung uses to pick a rung, so a rider within its supplied allowance always lands on the RIDER rung — I traced this rather than just trusting the javadoc.
  • Double-counting avoided. shedEnvelopeForSize only increments riderDroppedCounter when offeredState == PRESENT, correctly skipping the case where shedRiderForSize already counted the same loss on its way down the ladder.
  • decodeRider's declared exceptions (OffsetDecodingError, CorruptOffsetMetadataException, UnknownOffsetMetadataMagicException) are real: they can genuinely propagate under FAIL through deserialiseMetadataFromBase64decodeCompressedMetadataEncodedOffsetPair's policy handler, and the RiderEnvelope magic byte is registered in exactly one place (OffsetEncoding.RiderEnvelope), so astubbs#306's registry note is accurate.

Finding

parallel-consumer-core/src/main/java/bz/stub/parallelconsumer/state/PartitionState.java:1005-1022 — two stacked javadoc blocks precede warnOversizedRider:

/**
 * One rate-limited warning for a supplier that returned more than the {@link RiderContext#getMaxRiderBytes()}
 * it was handed (R8).
 * ...
 */
/**
 * The one warning for an over-cap rider has to describe two different outcomes, ...
 */
private void warnOversizedRider(int riderLength, int allowance, boolean besideAnOffsetMap) {

Only the second block attaches to the method; the first is a dead/orphaned comment left over from an earlier draft (likely predating the besideAnOffsetMap parameter). Harmless at runtime, but worth deleting — it's exactly the kind of "second copy that drifts from the first" AGENTS.md warns about for docs, just at method-comment scale. Fix this →

Known Residuals section

Read against the code, the residuals are accurately scoped — I didn't find any of them silently already fixed, nor any additional unflagged gap of the same shape. The "no test drives the rider through PC's own close-time commit and a restart read-back" gap and "no Error-subtype through the guard" gap both check out: riderFromSupplier does catch Throwable (so Error subtypes are handled at the code level per the javadoc), it's specifically the test coverage that's missing, which matches how the PR describes it.

Not reviewed in depth

Given time budget, I traced the core codec/budget/options/module paths closely but did not do a line-by-line pass of the four large test files (OffsetRiderOverheadTest, PartitionStateRiderBudgetTest, RiderSupplierGuardTest, PCMetricsRiderTest — collectively ~2500 lines) or the integration test OffsetRiderUpgradeDowngradeTest. I spot-checked their structure and framing; nothing there contradicted the main-code review above.

On the release-decision ask in the PR body: that's a product/release call for the author, not something I can weigh in on from a code-correctness review — flagging so it doesn't get lost.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

✅ Duplicate Code Report

Two engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check.

✅ PMD CPD

PR Base Change
Clones 26 26 ➖ 0
Duplicated lines 857 857 ➖ 0
Duplication 0.35% 0.35% ➖ 0
Rule Limit Status
Max duplication 0.5% ✅ Pass (0.35%)
Max increase vs base +0.1% ✅ Pass (+0.00%)

No new clones introduced by this PR.

✅ jscpd (language-agnostic)

PR Base Change
Clones 100 97 🫤 +3
Duplicated lines 1396 1368 :face_with_raised_eyebrow: +28
Duplication 0.79% 0.80% 🙂 -0.01%
Rule Limit Status
Max duplication 2% ✅ Pass (0.79%)
Max increase vs base +0.1% ✅ Pass (-0.01%)
⚠️ 3 new clones introduced
  • 11 lines: parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/offsets/OffsetRiderOverheadTest.java:15 <-> parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/state/PartitionStateRiderBudgetTest.java:18
  • 8 lines: parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/metrics/PCMetricsRiderTest.java:18 <-> parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/offsets/RiderTestFixtures.java:14
  • 12 lines: parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/metrics/PCMetricsRiderTest.java:20 <-> parallel-consumer-core/src/test/java/bz/stub/parallelconsumer/state/PartitionStateRiderBudgetTest.java:16

Powered by astubbs/duplicate-code-cross-check

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

🧪🔒 Quarantine Lane Report

Quarantined test Outcome Owner Meaning
MultiInstanceRebalanceTest.largeNumberOfInstances 🟡🎲 passed (flapper) ⚠️ unowned proves nothing - passes most runs by nature
RegistrationRaceStaleResidentIT.freshArrivalCollidingWithStaleShardResidentMustStillGetProcessed 🟡🎲 passed (flapper) ⚠️ unowned proves nothing - passes most runs by nature

🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its @Quarantined annotation + docs/quarantined-tests.md entry (a merge-blocking review thread has been opened). Lane: non-gating; rules: see the Quarantine Audit check.

No quarantined test changed outcome since the previous push.

Updated for cb8ebbb · run 34087745903 · 2026-09-07 05:46 UTC

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.26923% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.34%. Comparing base (e8f7beb) to head (cb8ebbb).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...bz/stub/parallelconsumer/state/PartitionState.java 87.60% 12 Missing and 3 partials ⚠️
.../parallelconsumer/offsets/OffsetRiderEnvelope.java 96.51% 2 Missing and 1 partial ⚠️
...z/stub/parallelconsumer/state/RiderBudgetRung.java 88.23% 1 Missing and 1 partial ⚠️
...ub/parallelconsumer/offsets/EncodedOffsetPair.java 98.11% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #460      +/-   ##
============================================
+ Coverage     82.34%   83.34%   +0.99%     
- Complexity     1486     1574      +88     
============================================
  Files            95       97       +2     
  Lines          5189     5469     +280     
  Branches        508      545      +37     
============================================
+ Hits           4273     4558     +285     
+ Misses          718      713       -5     
  Partials        198      198              
Flag Coverage Δ
chaos 50.21% <21.79%> (?)
integration 60.89% <44.23%> (-0.65%) ⬇️
performance 50.40% <21.79%> (?)
unit 79.61% <93.26%> (+0.96%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

⚠️ SpotBugs Report

390 bug(s) found (rule-level exclusions only - see docs/inflight/static-spotbugs-rule-registry.md). See the annotations on the Files Changed tab for details.

Updated for cb8ebbb · run 34087745958 · 2026-09-07 05:48 UTC

@astubbs

astubbs commented Sep 7, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

🟢 Throughput — OK

This branch measured about 10% slower than master, on the one test this measures. That is INSIDE this test's own run-to-run spread of about 17%, so read it as a reading and not as a result - re-running the same commit moves it by about as much.

What Value Meaning
Compared with master 0.896 above 1.00 is faster, below is slower
Subject test took 54.5s the test under measurement
Control tests took 30.2s the other tests in this same run
Subject ÷ controls, here 1.803 not a speed - a shape that cancels machine speed
Subject ÷ controls, master 1.616 median of recent master runs
Reported rate 66421 rec/s this machine only; not comparable across runners

Allowable range 🟢 ≥ 0.70 · 🟡 0.50–0.70 (about a 30% loss) · 🔴 < 0.50 (about a 50% loss)

What the numbers mean, and what they cannot tell you

The one that gets misread. Subject ÷ controls is a shape, not a speed. 1.7 means the subject took 1.7 times as long as the control tests in the same run - it says nothing about master on its own, and a reviewer has already read it as "1.7x faster than master". Only Compared with master answers that question.

Why a shape and not a rate. A rate depends on which runner you drew. A shape does not: every test here processes a fixed number of records, so a runner twice as slow doubles the subject and the controls together and leaves their ratio alone. That is the whole trick, and it is why the reported rate is shown last and labelled as this machine only.

Reading the comparison. master ÷ this run. Above 1.00 the subject is proportionally quicker here than on master; below 1.00 it is slower. 0.50 means it takes twice as long relative to its controls - that is the failing bound, not a small one.

By conservation, not by correction. Every test in this lane processes a fixed number of records, so within one run the ratio of one test's time to another's is invariant under machine speed — a runner twice as slow doubles both terms and leaves the ratio alone. There is no machine-index correction to be wrong, because nothing needed correcting. share = subjectSeconds / controlSeconds, both from this same run.

Per-method times, not class times. A class time is work + setup, and container startup and @BeforeAll do not scale with work — they are the non-conserved term, and leaving them in breaks the invariant.

Reference is the median of 10 recent perf baseline (master) run(s), read from their artifacts. There is no committed baseline to go stale, and a share is dimensionless, so an old entry stays comparable to a new one without re-baselining. Shares observed: 1.422 – 1.796.

What this still cannot do. It removes machine-to-machine variance. It does not remove this test's own run-to-run variance, measured at about 30% on a single unchanged commit while its controls stayed within 5%. That is a property of the test, not of the comparison, and no arithmetic here can touch it — which is why the reference is a median and the bounds are deliberately coarse. 🟡 means look at this; only 🔴 is outside the measured spread.

Runs used: e8f7beb, 29f6a0f, a75400f, 12bf414, ce6f39a, 70a88bf, 27211b9, c5dde06, 92364a1, 867c407

Since the previous push: ratio 0.989 -> 0.896, share 1.635 -> 1.803, rate 66785 -> 66421 (-0.5%). One push of difference sits inside this test's measured spread - read it as movement, not as a result.

Updated for cb8ebbb · run 34087745958 · 2026-09-07 05:48 UTC

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e85d5768a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/inflight/pr-207-offset-encoding-policy.md Outdated
Comment thread docs/refactoring.md Outdated
Comment on lines +644 to +646
// KTD14: the stripped-payload counter only. This escapes the inner-bytes step, which under KTD9 runs
// BEFORE the supplier is called, so on this path there is no rider to have discarded and nothing to
// count as dropped - the ladder's own strip rung is where both are counted.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invoke the rider supplier on the no-encoding fallback

When no offset-map encoder can represent the current range, this branch still returns a bare OffsetAndMetadata that is sent as a commit, but it runs before riderFromSupplier and therefore silently omits the configured rider. This contradicts the new option's promise that the supplier is called once per commit per partition and can leave the durable rider stale or absent after exactly the large-range fallback where the hole map is stripped; invoke the supplier and carry a rider-only envelope, or explicitly narrow the public contract.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

invoke the supplier and carry a rider-only envelope, or explicitly narrow the public contract.

Fair point, and it is wider than this branch: the ladder's own strip rung has the same shape (over the cap, the rider is shed first and the map stripped after, so nothing rides). Carrying a rider-only envelope on both strip paths would be consistent with the caught-up commit and is the direction I lean, but it changes the ladder's documented last rung and the pc.offsets.rider.dropped accounting the budget tests pin, so I want to make that call deliberately rather than inside a review round. Leaving this open for that decision.

@astubbs astubbs added this to the Streams on PC milestone Sep 7, 2026
…eclares the whole checked family, and the rider tests speak Truth

Codex's first pass on #460, Claude's summary, and the two CI failures that
were this branch's own.

THE ONE THAT MATTERED. encodeOffsetsToInnerBytes re-read hasIncompleteOffsets() after
tryToEncodeOffsets had already decided the caught-up case on the read it commits against. The
control thread can complete the last incomplete record between those two reads, and the re-check
then answered "nothing to encode" for a commit already pinned to the older offset - no map, and a
crash before the next commit replayed records this build had recorded as complete. That is the
confluentinc#893/894 tear class the javadoc two lines away warns about, reintroduced one call
deeper. The method now encodes whatever the encoder's own single sample holds, as the pre-rider
path always did; a map that emptied in between encodes as a complete map, which resumes correctly.
The test that pinned the old answer now pins the new one and says why.

decodeRider declared OffsetDecodingError and CorruptOffsetMetadataException but not the rest of
the checked FAIL family - KafkaStreamsEncodingNotSupported and UnsupportedOffsetEncodingException
arrive sneaky-thrown just as the unknown-magic case does. It now declares their parent,
EncodingNotSupportedException, so a caller can catch the family in one clause or any member by its
own type, and the javadoc names all three.

The overhead test read "not engaged at the top of the domain" as "never engages", which would have
skipped every boundary assertion for exactly the non-monotone compressed shape it exists to measure.
It now walks a 64-point grid before believing the endpoint; the residual is a transition narrower
than one grid step, named in the javadoc, rather than an unobserved interior.

House rules: the three new rider suites that had followed their AssertJ neighbours now use Google
Truth; discarded Set/Map returns in the rider tests are named with their reason; the release
decision moved out of the merged #207 note into release-offset-rider-write-side-split.md;
the encoding tie-break sighting outgrew a refactoring.md line and is now
core-offset-encoding-tie-break-is-not-deterministic.md; two stacked javadocs before
warnOversizedRider are one; the setup the two codec tests duplicated (CPD's one new clone) is
RiderTestFixtures.moduleWithNoSupplier and stateOver.

CI: CloseAndOpenOffsetTest forced every OffsetEncoding as a codec, including the envelope constant
that names none; it is skipped like the other non-encoders, and the reset of the forced statics
moved to @AfterEach, because the failing arm had leaked forcedCodec into the next test in the class.
Infer's BUFFER_OVERRUN_L1 on OffsetRiderEnvelope.wrap and its two callers is a guarded inner[0] read
that the interval domain cannot see past; the three identities are ratcheted with that reason.

Left open on the PR, deliberately: whether the rider should ride alone on the two strip paths (the
no-encoding fallback Codex flagged, and the ladder's own strip rung), which changes the ladder's
documented last rung and the dropped-rider accounting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
@astubbs

astubbs commented Sep 7, 2026

Copy link
Copy Markdown
Owner Author

Only the second block attaches to the method; the first is a dead/orphaned comment left over from an earlier draft

Addressed in 6725ad9: the two blocks before warnOversizedRider are one javadoc again - the limiter rationale and the two-outcome wording now sit in the same comment. Thanks for reading the residuals against the code as well.

astubbs and others added 2 commits September 7, 2026 05:30
Master moved 21 commits under this branch in two days. Three touch the rider's own files and none
argues with it: #450 registers a record's offset before publishing it to
its shard (PartitionState, adjacent to but not inside the commit path this branch changed);
#452 makes the codec's encodingCounters a ConcurrentHashMap (the
encode-path static this branch's tryToEncodeOffsets calls through); #106
walks only the offsets where completion state changes when no encoder needs every offset (the
overhead table's domain is unchanged by it, and its tests pass on the merge).

Two text conflicts, both in append-only records, both resolved in master's favour plus this
branch's additions: config/infer-known-findings.txt keeps master's retirement of every
THREAD_SAFETY_VIOLATION identity (#433) and this branch's three
BUFFER_OVERRUN_L1 identities; docs/refactoring.md keeps master's rewritten thread-safety entry for
the codec (#452 replaced the "not thread-safe" heading and body) and this
branch's two entries above it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
…e single-sample encoder took with it

The Infer lane fails both ways, and this is the good direction: one of the three BUFFER_OVERRUN_L1
identities recorded on the previous commit - OffsetMapCodecManager.makeOffsetMetadataPayload - no
longer fires, because encodeOffsetsToInnerBytes stopped returning an empty array on its own
authority (the emptiness re-read Codex caught), so nothing on that path hands wrap an empty inner
any more. The two sightings that remain are the guarded read itself and the caught-up commit that
wraps an empty array on purpose.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ
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