Skip to content

p2p: bound learned addresses and outbound dials; manual peers outside the automatic cap - #952

Merged
bkeroack merged 16 commits into
release/0.6from
fix/addr-list-and-dial-limits
Oct 14, 2026
Merged

bkeroack merged 16 commits into
release/0.6from
fix/addr-list-and-dial-limits

Conversation

@bkeroack

@bkeroack bkeroack commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Core references are to src/ at current master.

Addresses a peer announces. satd took every entry of every addr / addrv2 message (#939 caps a message at 1000 entries; ProcessAddrs, net_processing.cpp:5625-5704). Each entry went to the address book and, whether the book kept it or not, onto the reconnect loop's dial list, a Vec searched end to end on every insert, with no cap. getpeerinfo reported addr_processed and addr_rate_limited as 0.

Core meters the addresses each peer announces with a token bucket (net_processing.cpp:5640-5698): one token to start (m_addr_token_bucket{1.0}, :380), MAX_ADDR_RATE_PER_SECOND = 0.1 more a second up to MAX_ADDR_PROCESSING_TOKEN_BUCKET = 1000 (:193-197), and MAX_ADDR_TO_SEND more, past that limit, when it sends the peer getaddr (:3767). Entries are shuffled first; one that finds no token is dropped unless the peer has the addr permission; a banned address is not counted as processed. Core dials only from its address book.

Outbound dials. One limit, check_outbound_limit, covered automatic and manual dials alike. It counted connected outbound peers only, manual ones included, against the automatic target (8, or 64 during IBD):

  • Dials in flight were not counted, so each 10-second reconnect tick started a dial for every eligible learned address at once.
  • With the automatic peers connected, addnode … onetry failed with "max outbound connections reached", and the dial from addnode add / -addnode failed silently.
  • The redial pass ran only while that count was below the target, so on such a node an added node that dropped, or whose first dial failed, was not dialled again.
  • onetry to a connected peer failed with "already connected to …".
  • Backoff grew to 300 s for manual peers too, and every address's backoff was cleared whenever the tip moved, which during IBD is every tick.
  • The redial pass skipped a banned manual peer, and never looked at the ban of an onion host: automatic dials to a banned .onion (setban, or a ban for misbehaviour from p2p: don't ban local, inbound-onion or manual peers for misbehaviour (Core parity) #944) went on, and gossip naming one was kept.
  • setban <host>.onion did not disconnect a connected peer at that host: the disconnect matched bans against peers' IPs, and an onion peer's is the shared 0.0.0.0 placeholder (BanTarget::contains_addr is false for an onion target). Core's setban disconnects the peers the ban covers.

Core gives each kind its own limit, taken as a semaphore grant before the dial (net.cpp): automatic connections take semOutbound (ThreadOpenConnections); added nodes take semAddnode, MAX_ADDNODE_CONNECTIONS = 8 (net.h:71), in ThreadOpenAddedConnections, which tries again every 60 s whatever the automatic count (net.cpp:2972-3000); -connect (:2539-2556) and addnode onetry (rpc/net.cpp:352-357) take no grant, and onetry answers null, also for a host already connected (OpenNetworkConnection, net.cpp:3023-3030). OpenNetworkConnection checks bans and discouragement only for an automatic connection (no pszDest); a manual one is opened by the operator's name, banned or not.

Fix

  • net/addr_rate.rs (new): Core's token bucket, kept on PeerInfo with its counters. The addr and addrv2 arms shuffle the entries and keep those that find a token (or all of them for a peer with the addr permission); sending getaddr (addr-fetch peers, and outbound peers under a proxy) credits 1000. getpeerinfo reports addr_processed and addr_rate_limited.
  • A gossiped address goes on the dial list only if the address book holds it (not one past the book's per-group or total cap). The list is a set in arrival order, capped at the book's size (16,384).
  • reserve_dial checks a dial's limit and takes its place under one lock, counting dials in flight along with the peers (connected or in the handshake): automatic dials against the automatic target, manual connections apart from it; added nodes at most 8 at once; -connect, -seednode and onetry with no limit. The place is held until the peer is in the peer map.
  • The reconnect tick is two passes, as in Core: every manual peer that is not connected, whatever the automatic count, then learned addresses into the free automatic slots. Automatic dials (clearnet, onion and seeds) skip banned addresses; manual ones do not. Gossip naming a banned onion host is not kept.
  • With nothing leaving it, a full address book (or a full network group) would now have shut every new address out for good, so the book makes room by dropping a terrible entry (Core's AddrInfo::IsTerrible, addrman.cpp:49-72: unseen 30 days, three failed attempts and never reached, or ten failures in a week), new entries of the group first, then new, then tried ones; the dropped address leaves the dial list and its backoff state goes too. Failed redials now count as attempts in the book (Core's Attempt), which nothing recorded before.
  • Manual backoff stops at 60 s (Core's added-node retry); automatic stays at 300 s. Backoff is cleared only by a dial that connects.
  • setban disconnects an onion peer by its onion host; an IP-range ban no longer reaches onion peers through their placeholder address.
  • addnode onetry (connect_peer_addr_onetry) is outside every limit and succeeds for a peer already connected or being dialled. A dial that fails is still reported, as satd always has.

#895: counting dials in flight removes the part of the overshoot that came from one tick starting more dials than there were free slots: the automatic count can no longer pass the target in force when a dial starts. It does not close connections opened while the IBD target (64) applied, which is #895 itself. #894 is unchanged (the dial order is the list order, as before).

Tests

Unit tests, net::addr_rate::tests (new file) and net::manager::addrdials_tests (new file node/src/net/manager_addrdials_tests.rs):

  • addr_rate::tests: one address to start, ten after 100 s, the refill limit of 1000, the getaddr credit past it, the addr exemption, a clock that goes backwards.
  • a_peers_addresses_are_rate_limited_unless_it_has_the_addr_permission, addrv2_is_rate_limited_too, the_answer_to_a_getaddr_is_taken_whole (through handle_message).
  • an_address_the_book_has_no_room_for_is_not_dialled (100 addresses in one /16: 64 in the book, 64 on the list), the_dial_list_stops_at_its_cap.
  • a_reconnect_tick_starts_no_more_automatic_dials_than_free_slots (20 learned listeners, 3 automatic peers, target 8: 5 dials, then none).
  • an_added_node_is_dialled_with_the_automatic_slots_full (and an automatic dial is still refused), added_nodes_stop_at_eight_and_other_manual_peers_do_not, onetry_is_outside_the_limits_and_succeeds_for_a_connected_peer (a failed dial is still an error).
  • a_manual_peers_backoff_stops_at_a_minute, a_failed_manual_redial_backs_off_by_the_manual_cap, a_new_tip_leaves_reconnect_backoff_alone (drives the run loop), a_dial_that_connects_clears_its_backoff.
  • a_banned_manual_peer_is_dialled_and_a_banned_learned_one_is_not (reconnect loop and seed dial), a_banned_onion_host_is_neither_learned_nor_dialled_automatically (a banned onion host the operator named is still dialled), setban_of_an_onion_host_disconnects_that_peer_only.
  • a_full_book_replaces_a_terrible_entry, a_full_group_replaces_a_terrible_entry_and_its_backoff, a_book_full_of_stale_tried_entries_takes_a_new_address, a_failed_redial_counts_as_an_attempt (each fails with its guard reverted).

Integration tests, satd/tests/p2p_addr_dials.rs (new target, live regtest node):

  • a_peers_addresses_are_rate_limited_unless_it_has_the_addr_permission: 1000 addresses from an inbound peer → getpeerinfo addr_processed 1, addr_rate_limited 999; setmocktime +100 s → 10 more; a peer on a -whitebind=addr@… listener → all 1000 (Core's p2p_addr_relay.py steps).
  • an_addr_fetch_peers_answer_is_taken_whole: an addr-fetch peer's 600-address answer to getaddr is learned whole.
  • manual_connections_are_outside_the_automatic_outbound_target: -maxconnections=3, out of IBD, three automatic peers; addnode onetry connects and answers null, again null for the connected peer; addnode add to a port not yet listening is dialled again once it listens.

Existing tests edited in place: gossiped_addresses_keep_their_services_and_source (manager.rs, two addresses from one peer: the peer now has addr); an_address_that_names_no_host_is_neither_learned_nor_dialled and off_regtest_a_gossiped_private_address_is_neither_learned_nor_dialled (satd/tests/regtest.rs) announce several addresses from one inbound peer and expect all of them to be looked at; their nodes now run with -whitelist=addr@127.0.0.1, as Core's own address tests do.

Fails without the fix: yes. Each guard was reverted on its own and the named test failed on its assertion:

  • dial limit counting connected peers only → a_reconnect_tick_starts_no_more_automatic_dials_than_free_slots "left: 20, right: 5";
  • manual redial only below the automatic target → an_added_node_is_dialled_with_the_automatic_slots_full times out;
  • onetry taking the automatic limit / erroring for a connected peer → onetry_is_outside_the_limits_and_succeeds_for_a_connected_peer (expect fails both ways);
  • no added-node limit → added_nodes_stop_at_eight_and_other_manual_peers_do_not "left: 10, right: 8";
  • 300 s manual cap → a_manual_peers_backoff_stops_at_a_minute "left: 300s, right: 60s";
  • backoff reset on a new tip → a_new_tip_leaves_reconnect_backoff_alone "left: Some(0), right: Some(3)";
  • pushing what the book refused → an_address_the_book_has_no_room_for_is_not_dialled "left: 100, right: 64";
  • no list cap → the_dial_list_stops_at_its_cap;
  • no rate limit → the two rate-limit tests "left: (1000, 0), right: (1, 999)", "(500, 0) vs (1, 499)";
  • no getaddr credit → the_answer_to_a_getaddr_is_taken_whole "(1, 999) vs (1000, 0)" and the bucket test;
  • manual redial skipping bans → both ban tests time out; automatic onion dials or onion gossip ignoring bans → the onion ban test; automatic clearnet or seed dials ignoring bans → a_banned_manual_peer_is_dialled_and_a_banned_learned_one_is_not ("left: 1, right: 0", and the seed expect_err);
  • the onion setban test failed on the unfixed code: "left: {1, 2, 3}, right: {2, 3}".

Notes

  • Ordinary peers are still banned for -bantime rather than discouraged (unchanged, as in p2p: don't ban local, inbound-onion or manual peers for misbehaviour (Core parity) #944); the ban checks above use satd's ban list.
  • The automatic target is unchanged (8, 64 during IBD, the IBD figure not capped by -maxconnections). -seednode peers stay manual connections (CORE_DIFFERENCES.md), so they are now redialled like other manual peers whatever the automatic count.
  • satd still sends getaddr only to addr-fetch peers and, under a proxy, to outbound peers (Core sends it to every outbound full-relay peer); unsolicited addr traffic is metered exactly as Core meters it.
  • Found, not fixed: under a proxy satd also sends getaddr to a block-relay-only outbound peer, which Core never does (manager.rs, the proxy getaddr in the peer setup).

Release notes

RELEASE_- Ordinary peers are still banned for -bantime rather than discouraged (unchanged, as in #944); the ban checks above use satd's ban list.

  • The automatic target is unchanged (8, 64 during IBD, the IBD figure not capped by -maxconnections). -seednode peers stay manual connections (CORE_DIFFERENCES.md), so they are now redialled like other manual peers whatever the automatic count.
  • satd still sends getaddr only to addr-fetch peers and, under a proxy, to outbound peers (Core sends it to every outbound full-relay peer); unsolicited addr traffic is metered exactly as Core meters it.
  • Found, not fixed: under a proxy satd also sends getaddr to a block-relay-only outbound peer, which Core never does (manager.rs, the proxy getaddr in the peer setup).

🤖 Generated with Claude Code

bkeroack and others added 16 commits October 7, 2026 16:18
…ds per peer

- v1 frames are capped at Core's MAX_PROTOCOL_MESSAGE_LENGTH (4,000,000
  bytes) instead of 32 MiB. The v1 and v2 receive buffers grow as payload
  bytes arrive, at most 256 KiB ahead of them (V1Transport::readData,
  V2Transport MAX_RESERVE_AHEAD), and a large v1 buffer is released after
  its message.
- inv/getdata over MAX_INV_SZ (50,000), headers over MAX_HEADERS_RESULTS
  (2000) and addr/addrv2 over MAX_ADDR_TO_SEND (1000) are misbehaviour, as
  in Core; a notfound over 5016 entries is ignored.
- A peer's queue counts its bytes. getdata is served as Core's
  ProcessGetData does: entries are kept in a per-peer backlog and served
  until the queue passes 1,000,000 bytes (-maxsendbuffer's default), and
  the rest as it drains. The peer's next message is not read while a
  getdata of its is being served or its queue is over the limit
  (ProcessMessages). -maxuploadtarget is charged only for blocks queued.
- The write loop watches the disconnect signal during a write, and drops
  a peer whose socket takes no bytes for 20 minutes (TIMEOUT_INTERVAL,
  "socket sending timeout"). Sent bytes are counted as the socket takes
  them.
- A version whose user agent is over MAX_SUBVERSION_LENGTH is dropped, as
  Core's LIMITED_STRING read does; the user agent and unknown message
  types are sanitized with Core's SanitizeString before they are stored
  or logged.
- getcfilters/getcfheaders/getcfcheckpt requests that Core's
  PrepareBlockFilterRequest refuses now disconnect instead of being
  ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The write loop reads nothing more from a peer until its getdata has been
served, so the manager's 500 ms drain tick bounded a peer fetching blocks
to about one getdata, or one send buffer's worth of a large one, per tick.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deal with a peer whose misbehaviour reaches the threshold as Core's
MaybeDiscourageAndDisconnect does (net_processing.cpp), in its order:
a noban peer and a manual connection are not punished; a peer on a
local address (CNetAddr::IsLocal: 127.0.0.0/8, 0.0.0.0/8, ::1) is
disconnected without punishing the address; anyone else is
disconnected and its address banned for -bantime, as before (Core
discourages it).

satd banned every peer but noban ones. Every inbound onion peer arrives
from the address Tor dialled from, usually 127.0.0.1, as does every
unwhitelisted local client, so one such peer's misbehaviour refused all
of them until the ban expired, across restarts. A manual peer was
banned and then skipped by the redial loop.

Peers accepted on the onion listener are now marked (Core's
m_inbound_onion), and getpeerinfo reports them on the onion network, as
Core's ConnectedThroughNetwork does. An outbound onion peer is banned by
its onion host, the address Core punishes, rather than by the 0.0.0.0
placeholder socket every onion peer shares.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
serve_compact_block reported a MSG_CMPCT_BLOCK entry as answered even
when its cmpctblock could not be queued, so the entry left the getdata
backlog with nothing sent. It now says whether the message was queued,
and an entry that was not stays first, as every other entry does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s from

Core decides the disconnect-only rung on the address alone
(pnode.addr.IsLocal(), net_processing.cpp), and Tor on the same host
dials from loopback. With Tor on another host (-bind=<lan address>=onion)
every onion peer arrives from that host's address. Core discourages it,
and still admits it while inbound slots are free (net.cpp); satd's ban
would refuse every onion peer for -bantime. An inbound onion peer is
now disconnected without a ban whatever address it arrives from.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the automatic cap

- Core's address rate limit (ProcessAddrs): a token bucket per peer, one
  token to start, 0.1 a second up to 1000, plus 1000 when the node sends
  getaddr; entries are shuffled first and the addr permission is exempt.
  getpeerinfo reports addr_processed and addr_rate_limited.
- A gossiped address joins the dial list only if the address book holds
  it. The list is a set in arrival order, capped at the book's size.
- reserve_dial checks a dial's limit and takes its place under one lock,
  counting dials in flight: automatic dials against the automatic target,
  added nodes at most 8 at a time (Core's semAddnode), -connect,
  -seednode and onetry without a limit.
- The reconnect tick redials manual peers whatever the automatic count,
  then fills the free automatic slots. Automatic dials skip banned
  addresses, onion hosts included; manual dials do not (Core's
  OpenNetworkConnection). Gossip naming a banned onion host is not kept.
- Manual backoff stops at 60 s; backoff is cleared only by a dial that
  connects, not by a new tip.
- addnode onetry is outside every limit and succeeds for a peer already
  connected or being dialled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on-manual' into fix/addr-list-and-dial-limits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with B2's own tip (7c8b6ed) as the base, since this branch already
carried it and release/0.6 now holds its squashed equivalent. No conflicts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss's backoff goes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the backoff while checking membership

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bkeroack
bkeroack merged commit 77e7fbd into release/0.6 Oct 14, 2026
53 of 54 checks passed
@bkeroack
bkeroack deleted the fix/addr-list-and-dial-limits branch October 14, 2026 02:12
bkeroack added a commit that referenced this pull request Oct 14, 2026
Add CHANGELOG [Unreleased] bullets and 0.6.1-pre release-notes entries
for #933, #936-#948, #952-#955, #957 and #958: new Consensus and
validation, P2P, Mempool, Electrum and Esplora, Indexes and Configuration
sections, additions to Chain/IBD/RPC, five new Highlights and the
behaviour changes clients and operators will notice in Upgrade notes.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bkeroack added a commit that referenced this pull request Oct 15, 2026
… the automatic cap (#952)

* p2p: apply Core's message size and count limits, and bound queued sends per peer

- v1 frames are capped at Core's MAX_PROTOCOL_MESSAGE_LENGTH (4,000,000
  bytes) instead of 32 MiB. The v1 and v2 receive buffers grow as payload
  bytes arrive, at most 256 KiB ahead of them (V1Transport::readData,
  V2Transport MAX_RESERVE_AHEAD), and a large v1 buffer is released after
  its message.
- inv/getdata over MAX_INV_SZ (50,000), headers over MAX_HEADERS_RESULTS
  (2000) and addr/addrv2 over MAX_ADDR_TO_SEND (1000) are misbehaviour, as
  in Core; a notfound over 5016 entries is ignored.
- A peer's queue counts its bytes. getdata is served as Core's
  ProcessGetData does: entries are kept in a per-peer backlog and served
  until the queue passes 1,000,000 bytes (-maxsendbuffer's default), and
  the rest as it drains. The peer's next message is not read while a
  getdata of its is being served or its queue is over the limit
  (ProcessMessages). -maxuploadtarget is charged only for blocks queued.
- The write loop watches the disconnect signal during a write, and drops
  a peer whose socket takes no bytes for 20 minutes (TIMEOUT_INTERVAL,
  "socket sending timeout"). Sent bytes are counted as the socket takes
  them.
- A version whose user agent is over MAX_SUBVERSION_LENGTH is dropped, as
  Core's LIMITED_STRING read does; the user agent and unknown message
  types are sanitized with Core's SanitizeString before they are stored
  or logged.
- getcfilters/getcfheaders/getcfcheckpt requests that Core's
  PrepareBlockFilterRequest refuses now disconnect instead of being
  ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: wake the manager when a getdata or a getdata resume is handed over

The write loop reads nothing more from a peer until its getdata has been
served, so the manager's 500 ms drain tick bounded a peer fetching blocks
to about one getdata, or one send buffer's worth of a large one, per tick.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: don't ban local, inbound-onion or manual peers for misbehaviour

Deal with a peer whose misbehaviour reaches the threshold as Core's
MaybeDiscourageAndDisconnect does (net_processing.cpp), in its order:
a noban peer and a manual connection are not punished; a peer on a
local address (CNetAddr::IsLocal: 127.0.0.0/8, 0.0.0.0/8, ::1) is
disconnected without punishing the address; anyone else is
disconnected and its address banned for -bantime, as before (Core
discourages it).

satd banned every peer but noban ones. Every inbound onion peer arrives
from the address Tor dialled from, usually 127.0.0.1, as does every
unwhitelisted local client, so one such peer's misbehaviour refused all
of them until the ban expired, across restarts. A manual peer was
banned and then skipped by the redial loop.

Peers accepted on the onion listener are now marked (Core's
m_inbound_onion), and getpeerinfo reports them on the onion network, as
Core's ConnectedThroughNetwork does. An outbound onion peer is banned by
its onion host, the address Core punishes, rather than by the 0.0.0.0
placeholder socket every onion peer shares.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: keep a getdata cmpctblock entry that could not be queued

serve_compact_block reported a MSG_CMPCT_BLOCK entry as answered even
when its cmpctblock could not be queued, so the entry left the getdata
backlog with nothing sent. It now says whether the message was queued,
and an entry that was not stays first, as every other entry does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: tidy the MisbehaviourAction docs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: disconnect an inbound onion peer without a ban wherever Tor dials from

Core decides the disconnect-only rung on the address alone
(pnode.addr.IsLocal(), net_processing.cpp), and Tor on the same host
dials from loopback. With Tor on another host (-bind=<lan address>=onion)
every onion peer arrives from that host's address. Core discourages it,
and still admits it while inbound slots are free (net.cpp); satd's ban
would refuse every onion peer for -bantime. An inbound onion peer is
now disconnected without a ban whatever address it arrives from.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* p2p: bound learned addresses and outbound dials; manual peers outside the automatic cap

- Core's address rate limit (ProcessAddrs): a token bucket per peer, one
  token to start, 0.1 a second up to 1000, plus 1000 when the node sends
  getaddr; entries are shuffled first and the addr permission is exempt.
  getpeerinfo reports addr_processed and addr_rate_limited.
- A gossiped address joins the dial list only if the address book holds
  it. The list is a set in arrival order, capped at the book's size.
- reserve_dial checks a dial's limit and takes its place under one lock,
  counting dials in flight: automatic dials against the automatic target,
  added nodes at most 8 at a time (Core's semAddnode), -connect,
  -seednode and onetry without a limit.
- The reconnect tick redials manual peers whatever the automatic count,
  then fills the free automatic slots. Automatic dials skip banned
  addresses, onion hosts included; manual dials do not (Core's
  OpenNetworkConnection). Gossip naming a banned onion host is not kept.
- Manual backoff stops at 60 s; backoff is cleared only by a dial that
  connects, not by a new tip.
- addnode onetry is outside every limit and succeeds for a peer already
  connected or being dialled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: exempt a two-address test peer from the address rate limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: count only the announced addresses in the addr-fetch test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: setban of an onion host disconnects that onion peer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: a full address book replaces a terrible entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: a full network group replaces a terrible entry; a replaced address's backoff goes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* p2p: a full book of terrible tried entries takes new addresses; lock the backoff while checking membership

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 77e7fbd)
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