Skip to content

[Upstream Sync] Sync main with cilium/cilium#5

Merged
jiashengz merged 2077 commits intoRoblox:mainfrom
Roblox-Renovate-Bot:sync/upstream-main
Feb 23, 2026
Merged

[Upstream Sync] Sync main with cilium/cilium#5
jiashengz merged 2077 commits intoRoblox:mainfrom
Roblox-Renovate-Bot:sync/upstream-main

Conversation

@Roblox-Renovate-Bot
Copy link
Copy Markdown

Upstream Sync - main

This PR syncs main with upstream cilium/cilium.

New commits from upstream (2076 total):

45523ba54c ipam: Fix concurrent map access to multipool map
43f1093488 cilium-cli: auto-detect gke cluster
8438a72dde cilium-cli: add gke specific configmaps to cilium sysdump
9895151275 maps/l2{,v6}respondermap: use types.IPv{4,6} stringer interface
13a484875f maps: use netip.AddrFrom4
1f918dbb5b maps/nodemap: use netip.Addr for (*NodeKey).String
c49ff2387d maps/ipcache: convert to netip types
8d9806c4f8 maps/ipcache: make static prefix bits a const
2e5303f9e2 maps/egressmap: use types.IPv{4,6}.{Addr,Prefix}
9087bd9940 pkg/endpoint: set down the right veth pair interface
308d172bb2 endpoint/bpf: remove change empty condition for updateEnvoy
3cf0504979 renovate: Fix `k8s` patch updates on stable branches
7dcf36308c policy: Compute tier base priorities outside of the rules loop
81a564877c tools/dev-doctor: root-dir check should pass for worktrees
d259c76ee7 cilium-dbg: fix seg-fault `ip get -l reserved:host`
4d5b227322 install: grant Cilium operator to update ServiceImport finalizers
9d108b992a fix(deps): update all go dependencies main
8d15ebc2cd chore(deps): update all github action dependencies
7a1885369c chore(deps): update dependency cilium/cilium to v1.19.1
8ca8a86f65 renovate: Disable major/minor k8s upgrades on stable branches
af3d557bb3 clustermesh: fix a goroutine leak in epslicesync
5562e71ca6 bpf,probe: add kernel probe for BPF_FIB_LOOKUP_TBID
b3bc6f121b bpf,fib: utilize fib_table_id config value on pod egress
4fc223bdb5 bpf,endpoint: introduce fib-table-id annotation
d1cf6738de chore(deps): update all lvh-images main
5fd2c6a081 chore(deps): update quay.io/cilium/cilium-envoy docker tag to v1.36.5-1771332981-9e7e35f1d1f516c348c8b835dafde5c30fef9847
1914ed5519 images: update cilium-{runtime,builder}
c2d830a9d1 chore(deps): update all-dependencies
bbcc82373c ipam: move ipam initializer logic into ipam cell
dea62ee99a node/address: remove global variable `localNode`.

smagnani96 and others added 30 commits February 5, 2026 14:33
This commit updates the name of the "Setup & Test" job in the
GitHub Actions workflow for e2e upgrade tests to include only the matrix
parameters "name" and "mode". This change improves the readability
of the workflow runs by providing more context about the specific
configuration being tested.

Prior to this, the name of each job contained the whole matrix combination,
which in the UI resulted to be cut off and not readable. Given that now
we use the same workflow file for running both `minor` and `patch` upgrades,
let's make the displayed name simpler.

The result will be `Setup & Test (ipsec-1, minor)`.

Signed-off-by: Simone Magnani <[email protected]>
This commits adds as a first step of the `Setup & Test` job for e2e-upgrade
a simple step to dump the current matrix configuration being tested.

The previous commit, modified the title to simply display the matrix entry
name and mode (e.g., `Setup & Test (ipsec-1, minor)`) rather than the
whole configuration. In UI, that would result to be truncated anyway.

It is true that, given the matrix.name (e.g., ipsec-1), a user can open the
specific file and lookup the configuration required, but I think that
having a step where we dump it would speed up and easy debuggability in CI.

The output would be similar to:

```
> Log Matrix Configuration
Current matrix configuration:
{
  "name": "wireguard-1",
  "kernel": "5.10",
  "kube-proxy": "iptables",
  "kpr": "true",
  "devices": "{eth0,eth1}",
  "secondary-network": "true",
  "tunnel": "vxlan",
  "encryption": "wireguard",
  "encryption-node": "false",
  "lb-mode": "snat",
  "endpoint-routes": "true",
  "egress-gateway": "true",
  "ingress-controller": "true",
  "mode": "minor"
}
```

Signed-off-by: Simone Magnani <[email protected]>
Standardize logging in pkg/endpoint so that identityLabels and related fields are logged as structured JSON objects instead of comma-separated strings by explicitly casting labels.Labels to map[string]labels.Label.

Signed-off-by: Jie WU <[email protected]>

```release-note
endpoint: Log labels as structured JSON objects
```
This sets the hubble-ui pods/containers to match k8s
pss-restricted profile along with the optional
`readOnlyRootFilesystem: true`

Signed-off-by: Pat Riehecky <[email protected]>
Previously cilium-operator fails to start if MCS/installCRDs is enabled
because it does not have permissions to update the CRD with this log
message:

level=error msg="Unable to update CRD"
module=operator.operator-controlplane.leader-lifecycle.create-crds
name=serviceimports.multicluster.x-k8s.io
error="customresourcedefinitions.apiextensions.k8s.io
\"serviceimports.multicluster.x-k8s.io\" is forbidden: User
\"system:serviceaccount:kube-system:cilium-operator\" cannot update
resource \"customresourcedefinitions\" in API group
\"apiextensions.k8s.io\" at the cluster scope"

This patch adds the necessary permissions to cilium-operator if you have
mcs/installCRDs enabled

Fixes: cilium#44210
Fixes: 3874013 ("clustermesh: add config for auto installing
MCS-API CRDs")

Signed-off-by: Florian Ströger <[email protected]>
A subsequent commit will include an alternate policy iteration system,
so it will be nice to move the types to policy/types.

This also removes the now-useless Decision type, as it's not used
anywhere in the codebase.

Signed-off-by: Casey Callendrello <[email protected]>
This is a simple userspace tool that executes rules step-by-step. It's
purpose will be to validate more complex policy scenarios, ideally by
fuzzing.

To ensure it's output matches that of the existing policy engine, it
matches the LookupFlow method signature, and existing tests validate
that the simulation engine returns the same verdict.

Signed-off-by: Casey Callendrello <[email protected]>
This generates random policy corpuses and compares MapState-based policy
calculation with the iterative simulator.

Signed-off-by: Casey Callendrello <[email protected]>
Avoid using *testing.F for the logger as then any log within the fuzz
test would fail.

Fix the order of expected and actual for require.Equal.

Add more debugging.

Signed-off-by: Jarno Rajahalme <[email protected]>
Hide precedence details from the policymap package.

Signed-off-by: Jarno Rajahalme <[email protected]>
Add optional mapState indexing by identity to support incremental removal
of generated keys. This is only needed for deletion pass entries, so the
index is only used if the policy has pass verdicts.

Signed-off-by: Jarno Rajahalme <[email protected]>
Proper processing of pass verdicts requires the default deny rule to be
explicitly added to the mapstate so that it can be seen by pass verdict
entries.

The default rule is added to the next tier if any non-default tiers or
priorities are in use, of if the traffic direction has any pass
rules. This way the pass rule can pass to the added default deny (or
allow) rule.

Signed-off-by: Jarno Rajahalme <[email protected]>
Deny takes precedence over allow and pass, allow takes precedence over
pass. Define new HasPrecedenceOver() to handle this instead of using just
IsDeny() like before.  Would be simpler if Allow was not the zero value,
but changing that would require changing all unit testing code that uses
it as the default.

Signed-off-by: Jarno Rajahalme <[email protected]>
Fix tier base priority calculation. When figuring out the priority range
for each tier, the full range of the remaining tiers must be included to
add enough space for pass verdicts on higher tiers. Then, when setting
the base priotity of each tier, this has to be reversed.

Signed-off-by: Jarno Rajahalme <[email protected]>
Commit fuzzer cases found during development.

Signed-off-by: Jarno Rajahalme <[email protected]>
A pass of a specific identity to a lower tier rule with wildcard identity
should pass the given identity only and keep the wildcard entry at the
original precedence to take care of traffic with other identities. Since
the original entry needs to be kept, a new generated entry with the
identity from the pass entry and the L4 from the passed to entry must be
added.

We missed this case earlier due to BroaderOrEqualKeys only iterating
wildcard identity entries when the new key is a wildcard entry. Entries
that have a broader or equal L4 but more specific L3 are not as a whole
"broader or equal". To handle the need for generated entries for the pass
verdict processing "BroaderOrEqualKeys" is changed to also iterate all
specific L3 keys if the L4 is broader or equal and the given key has the
wildcard identity. The old behavior is retained with
CoveringBroaderOrEqualKeys(). Similarly, NarrowerOrEqualKeys() is renamed
as CoveringNarrowerOrEqualKeys() while NarrowerOrEqualKeys() now also
iterates keys with the wildcard identity when the given key has a
specific identity.

The addition of generated entries requires these entries to be deleted
when that identity is incrementally deleted. Since selector cache is
transactional we can delete all keys with the deleted identity, when the
first key with that identity is deleted. To make this efficient we use
the new id index.

To add support for pass verdicts at multiple tiers, the pass metadata is
now stored as a slice. Overhead to non-pass entries is reduced by storing
the slice via a pointer ('passes'), as most mapStateEntries would not
have any pass metadata.

If 'passes' is non-nil, then the pointed-to slice must have at least
one element, and all elements must have non-zero 'passPrecedence'.

When merging pass metadata we clone the slice to be mutated so that the
same slice can safely be used in multiple entries.

Split insertWithPasses() from insertWithChanges(); insertWithPasses() is
only calling it if the policy has any pass verdicts. This reduces the
chance of regressions for non-pass policies.

Log a warning if a policy with pass verdicts is also using auth
requirements, as this combination has not been implemented. Adjust a test
to not claim all features when that is not the case.

Signed-off-by: Jarno Rajahalme <[email protected]>
The Subnets field in the config was declaring a json tag, leading to a
failure of the agent `hive` command (see below). This is due to the fact
that the hive relies on a mapstructure Decoder, not a Json one, and
therefore require a mapstructure tag when the config field name is not
equal to the flag name.

Fix the tag on the field using a mapstructure one.

```
make -C daemon/ && ./daemon/cilium-agent hive
...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0xacd46e]

goroutine 1 [running]:
github.com/cilium/hive/cell.(*InfoNode).Print(0xc000b540f0, 0xa?, 0xc000cba750)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/cilium/hive/cell/info.go:109 +0x12e
github.com/cilium/hive/cell.(*InfoNode).Print(0xc000a9bdd0, 0x6?, 0xc000cba750)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/cilium/hive/cell/info.go:109 +0x134
github.com/cilium/hive/cell.(*InfoNode).Print(0xc000a799b0, 0x2?, 0xc000cba750)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/cilium/hive/cell/info.go:109 +0x134
github.com/cilium/hive.(*Hive).PrintObjects(0xc0009554a0, {0x51e1240, 0xc0000c0030}, 0x0?)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/cilium/hive/hive.go:459 +0x18f
github.com/cilium/hive.(*Hive).Command.func1(0xc000e0fc00?, {0x4b05b96?, 0x4?, 0x4b05aca?})
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/cilium/hive/command.go:21 +0x2d
github.com/spf13/cobra.(*Command).execute(0xc000953508, {0x86bac20, 0x0, 0x0})
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/spf13/cobra/command.go:1015 +0xb02
github.com/spf13/cobra.(*Command).ExecuteC(0xc000952f08)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/spf13/cobra/command.go:1148 +0x465
github.com/spf13/cobra.(*Command).Execute(...)
	/home/ffalzoi/cilium/cilium-2/vendor/github.com/spf13/cobra/command.go:1071
github.com/cilium/cilium/daemon/cmd.Execute(0x4d769d0?)
	/home/ffalzoi/cilium/cilium-2/daemon/cmd/root.go:89 +0x13
main.main()
	/home/ffalzoi/cilium/cilium-2/daemon/main.go:15 +0x1f
```

Fixes: d395d73 ("pkg/subnet: Add subnet config watcher and manager")
Signed-off-by: Fabio Falzoi <[email protected]>
Adding new reconciler in Cilium datapath/linux which can manage
life cycle of linux links created by Cilium.

Created devices are persisted on disk using write-ahead-log, upon
restart the owners of the devices are expected to redo the
configuration before calling finializer. Stale devices will be pruned.

Implementation is inspired by linux/route/reconciler.

Signed-off-by: harsimran pabla <[email protected]>
Adding script tests to validate device creation and persistence.

Signed-off-by: harsimran pabla <[email protected]>
intervalSeconds is always of an integral type, no need to check kindIs float64

Fixes: cilium#44206
Signed-off-by: jayl1e <[email protected]>
Move IPv4/IPv6 hash init seeds into node config and wire them from
Maglev config. BPF tuple hashing now reads CONFIG(hash_init{4,6}_seed)
instead of compile-time defines, and the legacy HASH_INIT* defines are
removed from the header writer and node_config.h.

Signed-off-by: viktor-kurchenko <[email protected]>
The hubble components do not require direct mapping of container
users to system users.

Signed-off-by: Pat Riehecky <[email protected]>
Signed-off-by: Yohei Yamamoto <[email protected]>
Removed conditions from the comment block describing the cil_from_netdev function since the logic has been changed

Signed-off-by: Liyi Huang <[email protected]>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
Instead of passing the lb4_key/lb6_key to lb4_xlate/lb6_xlate for
checksum calculation and port translation, pass the original destination
address and port directly from the CT tuple.

This change:
1. Removes the key parameter from lb4_xlate/lb6_xlate functions
2. Removes the key parameter from lb4_dnat_request/lb6_dnat_request

The CT tuple already contains the same values that were being read from
the key structure:
- tuple->daddr == key->address (original destination)
- tuple->sport == key->dport (reversed port order in CT tuple)

By removing the xlate path's dependency on key, we can now directly
modify key->address = 0 for wildcard lookups without creating a copy,
simplifying the backend selection logic.

Co-authored-by: Siwan Kim <[email protected]>
Signed-off-by: Gyutae Bae <[email protected]>
When DSR and PER_PACKET_LB are enabled, connections fail if a client pod
sends a request to a remote node's NodePort service while the server
pod is located on the same node as the client.

The root cause is that the remote node only performs DNAT, setting the
packet's source address to client's node address, leading to a hairpin
problem. Consequently, the originating node cannot perform the necessary
REV NAT for the reply packets.

To resolve this, remote NodePort service requests are now handled on the
source node when DSR is enabled, similar to the behavior of socket-level
load balancing.

Implementation details:

- Add lb4/6_lookup_wildcard_nodeport_service(): When ENABLE_DSR is
  defined and the regular service lookup fails, check if the destination
  is a remote node's IP with a NodePort port range. If so, perform a
  wildcard lookup (address=0) to find the NodePort service.

- Use wildcard key for backend selection: When dsr_internal flag is set,
  set key->address to 0 before calling lb4/6_select_backend_id(). This
  applies to both CT_NEW (new connections) and CT_REPLY (backend
  re-selection for existing connections). This is needed for backend
  selection algorithms that use slot lookup (e.g., Random), which look
  up backend slots via lb4/6_lookup_backend_slot() using the service
  key. Without a wildcard key, the lookup would fail because backend
  slot entries are stored with the wildcard service key, not with the
  remote node's IP.

- Store original destination in CT entry: The original destination
  address and port (remote node IP and NodePort) are stored in
  ct_state_new.nat_addr/nat_port, which will be written to the CT entry
  for use in reply path RevNAT processing.

- Use cilium_dsr_nat_buffer per-CPU map: The NAT info is detected in
  __per_packet_lb_svc_xlate_4/6(), but the CT entry is created after DNAT
  when the original destination info is no longer available in the packet.
  The per-CPU buffer preserves this info across the DNAT operation.

Existing connection handling:

- This change only affects DSR traffic destined to remote node's
  NodePort. The wildcard lookup is triggered when lb4/6_lookup_service()
  fails, but it only processes packets where the destination is a remote
  node IP with a port in the NodePort range. Other traffic that fails
  the regular lookup is unaffected.

Fixes: cilium#41962

Co-authored-by: Siwan Kim <[email protected]>
Signed-off-by: Gyutae Bae <[email protected]>
ldelossa and others added 25 commits February 22, 2026 20:39
Add a new annotation, 'network.cilium.io/fib-table-id', providing
a fib table ID accessible via the CONFIG(fib_table_id) macro within
bpf_lxc.

A future commit will utilize this configuration value to perform eBPF
redirects that respect a particular kernel fib table, allowing dynamic
route selection for pod egress traffic.

Signed-off-by: Louis DeLosSantos <[email protected]>
When `ENABLE_HOST_ROUTING` is enabled the eBPF data path can now
evaluate the `fib_table_id` config parameter.

If this config parameter is set to a value other then 0 this value is
used to scope the final fib lookup, routing the pod egress packet to its
next hop.

This enables dynamic routing on a per-pod basis.

Signed-off-by: Louis DeLosSantos <[email protected]>
Introduce a new kernel probe to detect if the BPF_FIB_LOOKUP_TBID flag
is supported for use with bpf_fib_lookup.

If the flag is not available, and Cilium is configured to utilize it via
the EnableFibTableIDAnnotation option, Cilium will fail to start with an
error.

Signed-off-by: Louis DeLosSantos <[email protected]>
The receive function could stall because of the send to the unbuffered result
channel and prevent the goroutine from terminating. This commit fixes that by
following the same approach as StreamWatcher (which the
meshEndpointSliceWatcher is inspired from) to avoid this leak.

Reported-by: Tobias Klauser <[email protected]>
Signed-off-by: Arthur Outhenin-Chalandre <[email protected]>
PR cilium#44389 introduced the `"matchBaseBranches": ["main"]` constraint to the `"disable major/minor k8s updates"` rule. The intent was to scope that rule to main since stable branches would have their own logic, but the new `"k8s.io patch updates stable"` rule only covers patch/digest — it never disables major/minor on stable. So major/minor k8s update fell through with nothing blocking them on v1.17/v1.18/v1.19.

That's how we got cilium#44481 attempting to bump those from `v0.32.0` to `v0.35.1` on 1.17 and cilium#44476 from `v0.33.3` to `v0.35.1` on 1.18.

This PR removes the `matchBaseBranches: ["main"]` from the disable rule so it applies t all branches. The `"k8s.io patch updates stable"` rule (which explicitly enables patch+digest on stable) will still take precedence for those update types, giving the correct behavior:
* All branches: major/minor k8s updates disabled
* Stable branches only: patch/digest k8s updates enabled

Signed-off-by: Hadrien Patte <[email protected]>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
Signed-off-by: cilium-renovate[bot] <134692979+cilium-renovate[bot]@users.noreply.github.com>
This is required when the OwnerReferencesPermissionEnforcement admission
plugin is set, because the operator creates derived Services resources
owned by the corresponding ingress, with the blockOwnerDeletion flag set.

Signed-off-by: Arthur Outhenin-Chalandre <[email protected]>
Update root-dir check in dev-doctor tool to pass if ".git" is a valid
worktree ref.

Signed-off-by: Charlie Kenney <[email protected]>
Simplify ruleSlice.resolveL4Policy() by computing tier base priorities
before the main rules loop.

Signed-off-by: Jarno Rajahalme <[email protected]>
The `"k8s.io patch updates stable"` rule was missing `separateMinorPatch: true`. With the global `separateMinorPatch: false`, Renovate produces a single PR targeting the latest version when a newer minor is available. That PR is classified as "minor", so `matchUpdateTypes: ["patch"]` never matches, `enabled: true` never fires, and the `"k8s major/minor updates disabled"` rule blocks it — no patch PR is ever created.

Add `separateMajorMinor: true` and `separateMinorPatch: true` to the rule, matching the pattern already used for lvh-images and external docker images on stable branches. This forces a separate "patch"-typed PR (staying within the current minor series) which the rule can correctly enable, while the "minor"-typed PR remains blocked as intended.

Followup to cilium#44489

Signed-off-by: Hadrien Patte <[email protected]>
This commit removes the !changes.Empty() condition to avoid the bug when
the bpf map is no change but we still need to update the envoy network
policy.

When there is SNI network policy with FQDN network policy, we will
redirect egress all traffic to the envoy.  The identity could change
with wildcard FQDN policy and bpf map will keep the same. that will
cause the enovy network policy not getting updated.

For example,we could have the following identities in the beginning

1677721   fqdn:sts.*.amazonaws.com
           reserved:world
16777220   fqdn:*.amazonaws.com
           reserved:world

When the DNS resolves the IP for sts.*.amazonaws.com, we will generate
the new identity

16777223   fqdn:*.*.amazonaws.com
           fqdn:sts.*.amazonaws.com
           reserved:world

If we have the SNI network policy for the pod, that will make the bpf
map look like the following.

root@kind-worker8:/home/cilium# cilium bpf policy get 2782
POLICY   DIRECTION   LABELS (source:key[=value])
PORT/PROTO   PROXY PORT   AUTH TYPE   BYTES   PACKETS   PREFIX   LEVEL
Allow    Ingress     ANY
ANY          NONE         disabled    0       0         0        0
Allow    Ingress     reserved:host
ANY          NONE         disabled    0       0         0        0
Allow    Egress      ANY
443/TCP      13379        disabled    5904    33        24       0

With the current check logic, there is no change to the map. Then we
will skip updating the envoy network policy causing envoy holding the
stale identity and block the traffic.

Signed-off-by: Liyi Huang <[email protected]>
On certain environments, where StatefulSets are used, the re-usage of a
vethpair with the same name can occur. This can cause some concurrency
issues and the setDown function is executed for the "new" veth pair,
which uses the same name as the "older" veth pair. To prevent this from
happening we should also check if the ifindex matches the veth pair
fetched by netlink.

Fixes: 6633ca8 ("datapath,endpoint: explicitly remove TC filters during endpoint teardown")
Signed-off-by: André Martins <[email protected]>
These methods require a netip.Addr or netip.Prefix, so use the
corresponding methods returning these types instead of manual
constructing them from net.IP.

This allows to remove the now unused types.IPv{4,6}.IP methods.

Signed-off-by: Tobias Klauser <[email protected]>
This doesn't need to be a function, all values are known at build time.
Make it a const.

Signed-off-by: Tobias Klauser <[email protected]>
Use the netip.{Addr,Prefix} types to construct ipcache key and value
types. This simplifies call sites and avoids unnecessary conversions.

Signed-off-by: Tobias Klauser <[email protected]>
Construct a netip.Addr instead of net.IP to get the string
representation from. Internally, net.IP.String relies on the netip.Addr
for the string representation, so might as well do it directly without
slice allocation.

Signed-off-by: Tobias Klauser <[email protected]>
Use netip.AddrFrom4 when constructing a IPv4 netip.Addr from a [4]byte
rather than using netip.AddrFromSlice. This avoids unnecessary size and
return value checks.

Signed-off-by: Tobias Klauser <[email protected]>
Make use of types.IPv{4,6} implementing the stringer interface for
printing the key IP rather than converting it to net.IP and using its
String method.

Signed-off-by: Tobias Klauser <[email protected]>
For some reason, I forgot to acquire the mutex when accessing the
multipool map in 788d1aa ("ipam, metrics: Add new capacity
metric"). Fix it now to prevent the concurrent map access panic as seen
in cilium#44107.

Fixes: 788d1aa ("ipam, metrics: Add new capacity
metric")
Fixes: cilium#44107

Co-authored-by: Fabio Falzoi <[email protected]>
Signed-off-by: Chris Tarazi <[email protected]>
@jiashengz jiashengz merged commit f4fd075 into Roblox:main Feb 23, 2026
jiashengz added a commit that referenced this pull request Feb 24, 2026
jiashengz added a commit that referenced this pull request Feb 25, 2026
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.