Skip to content

feat: OracleDB support for CDC via LogMiner#4036

Merged
josephwoodward merged 52 commits intomainfrom
jw/oracledb_cdc
Mar 10, 2026
Merged

feat: OracleDB support for CDC via LogMiner#4036
josephwoodward merged 52 commits intomainfrom
jw/oracledb_cdc

Conversation

@josephwoodward
Copy link
Contributor

@josephwoodward josephwoodward commented Feb 27, 2026

This pull request adds support for the CDC pattern via LogMiner. LogMiner is traditionally a tool for providing a relational interface to Oracle's Redo Log Files for the purposes of tasks such as data recovery and auditing, it's also commonly also used for supporting the change data capture pattern. As such, there's some additional work for enabling CDC via LogMiner which I'll break down below.

How CDC Works with LogMiner

From a high level, LogMiner works by loading redo logs then allowing SQL based access to the change events. From an architectural perspective the following core components are:

LogMiner (logminer/logminer.go)

The continuous streaming engine. Each mining cycle:

  1. Queries OracleDB to determine which redo log files cover the SCN window since the last cycle (this is needed as logs can be cycled based on configurations such as size and duration)
  2. Deduplicates log files by preferring archived logs over online logs.
  3. Registers the log files with LogMiner and starts a bounded session (see Session Manager below)
  4. Reads the log contents, parsing DML entries through the SQL redo parser (see SQL Redo Parser below)
  5. Backs off when caught up with the current database SCN

Session Manager (logminer/session.go)

This is responsible for loading log files into LogMiner as they're cycled. It does this in a mining loop that continuously checks for the latest latest SCN.

Transaction Buffer (logminer/cache.go)

Event rows queried via LogMiner begin with a transaction start and eventually end with a commit/rollback. This means as we're reading events we have to buffer them until we reach the associative commit or rollback. Rollbacks result in the buffer being discarded and commits result in the buffer being processed by the SQL Redo Parser (discussed below) before published to Benthos pipeline. As such, it's possible that long-running transactions on a high-throughput system can lead to memory exhaustion, so it's important to have controls in place to limit this (max_transaction_events allows users to specify an upper limit).

As mentioned, the current implementation is in-memory but it'd be good to add support for other storage mechanisms (such as redis, or potentially even OracleDB - similar to what we do with the checkpoint cache)

SQL Redo Parser (logminer/sqlredo/)

Parses the reconstructed SQL redo statements produced by LogMiner back into structured DMLEvent values (table, operation type, column values).

The queries we get back from log miner literally look like this:

insert into "MYAPP"."CUSTOMERS" ("ID","NAME","EMAIL") values ('1','John Doe','[email protected]')`.

In some instances they'll have functions in them such as below:

insert into "MYAPP"."ORDERS" ("ID","ORDER_DATE") values ('100',TO_DATE('2020-01-15','YYYY-MM-DD'))

Based on the function name we have to perform conversions in Go (see logminer/sqlredo/valueconverter_test.go for tests).

Things to follow up in separate PR

  • Add more detailed documentation on how to configure Oracle to support CDC
  • Support for CLOB types

@claude
Copy link

claude bot commented Feb 27, 2026

Claude finished @josephwoodward's task in 4m 22s —— View job


  • Validate commit policy
  • Read project patterns (godev.md, tester.md)
  • Review code changes
  • Post inline comments
  • Post summary

Commits

  1. oracle_cdc: snapshotting across multiple schemas working — does not use imperative mood; reads as a status update. Should be e.g. oracle_cdc: support snapshotting across multiple schemas.
  2. oracle_cdc: test verifying various types passing — does not use imperative mood; vague. Should be e.g. oracle_cdc: add test for various column types.

Review

Well-structured CDC implementation with good architectural separation (LogMiner engine, session manager, transaction buffer, SQL redo parser). The integration tests are thorough and follow project patterns (testcontainers-go, integration.CheckSkip, assert.Eventually with mutex, license.InjectTestService). The component registration, public wrapper, bundle imports, info.csv entry, and license headers are all correct.

  1. Bug: ConvertValue receives column name instead of column type (parser.go:79) — The value converter's type-directed conversion paths are dead code because column names (e.g. "ORDER_DATE") are passed where Oracle type names (e.g. "DATE") are expected. Only the fallback pattern matching on the value string works. See inline comment for details.

@claude
Copy link

claude bot commented Feb 27, 2026

Claude Code is working…

  • Validate commit policy
  • Read project patterns (godev.md, tester.md)
  • Review code changes
  • Post inline comments
  • Post summary

View job run

@claude
Copy link

claude bot commented Feb 27, 2026

Claude finished @josephwoodward's task in 8m 22s —— View job


  • Validate commit policy
  • Read project patterns (godev.md, tester.md)
  • Review code changes
  • Post inline comments
  • Post summary

Commits

  1. 7be6aecde046b oracle_cdc: setting up local test infrastructure — "setting up" is not imperative mood. Should be e.g. oracle_cdc: set up local test infrastructure.
  2. fe1209b72c8c5 oracle_cdc: snapshotting across multiple schemas working — not imperative mood; reads as status update. Should be e.g. oracle_cdc: support snapshotting across multiple schemas.
  3. 878dff1e35684 oracle_cdc: test verifying various types passing — not imperative mood; reads as status update. Should be e.g. oracle_cdc: add test for various column types.
  4. f6277c3d67580 oracle_cdc: clean up and refactorings following code reviews — "refactorings" is a noun, not imperative. Should be e.g. oracle_cdc: clean up and refactor following code reviews.

Review

Well-structured CDC implementation with good architectural separation (LogMiner engine, session manager, transaction buffer, SQL redo parser). Component registration (init() + MustRegisterBatchInput), public wrapper (public/components/oracledb/package.go), bundle imports (all/package.go, cloud/package.go), info.csv entry, and RCL license headers are all correct. Integration tests follow project patterns (testcontainers-go, integration.CheckSkip, assert.Eventually with mutex, license.InjectTestService, context.Background() in cleanup, no require inside assert.Eventually). The previous review's ConvertValue column-type bug has been addressed — the converter now works solely on value string patterns.

  1. Context-unaware time.Sleep in mining loop (logminer.go:138) — time.Sleep blocks without checking context cancellation, delaying graceful shutdown by up to the user-configured backoff_interval. Should use select with ctx.Done() and time.After. See inline comment for details.

lm.log.Infof("Caught up with redo logs, backing off")
}
time.Sleep(lm.cfg.MiningBackoffInterval)
}
Copy link

Choose a reason for hiding this comment

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

Bug: time.Sleep blocks without checking context cancellation, so a shutdown signal arriving during the backoff interval is not noticed until the sleep completes. Since MiningBackoffInterval is user-configurable (and could be set to minutes), this delays graceful shutdown by up to that duration.

Per godev patterns — check for cancellation in long-running loops using a select on ctx.Done(). Replace the time.Sleep with a context-aware backoff:

select {
case <-ctx.Done():
    return ctx.Err()
case <-time.After(lm.cfg.MiningBackoffInterval):
}

This also lets you remove the select/default wrapper above (lines 128-131) since cancellation is now checked at the sleep point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was intentional as it's a tight loop so can result in a lot of unnecessary allocations, will need to benchmark to understand impact vs the additional wait time.


== Permissions

When using the default Oracle based cache, the Connect user requires permission to create tables and stored procedures, and the ` + "rpcn" + ` schema must already exist. Refer to ` + "`" + ociFieldCheckpointCacheTableName + "`" + ` for more information.
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably should have more actionable information here if possible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to go through the docs separately to add more setup information so will make a note to include this.

Comment on lines +246 to +248
} else if policy.IsNoop() {
policy.Count = 1
}
Copy link
Contributor

Choose a reason for hiding this comment

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

That is to say that Count must be > 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's to set a default value, following the same approach as other CDC components (PostgreSQL for instance).

// Reset our stop signal
i.stopSig = shutdown.NewSignaller()

go func() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider extracting to function that can return error and do not do ad-hoc

				i.stopSig.TriggerHasStopped()
				return

Copy link
Contributor

Choose a reason for hiding this comment

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

You could actually use the wg, _ := errgroup.WithContext(softCtx) defined below

return
}

if err = i.cacheSCN(softCtx, maxSCN); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

All the error reporting would be easier in a method.

}

var currentSCN SCN
if err := s.db.QueryRowContext(ctx, "SELECT CURRENT_SCN FROM V$DATABASE").Scan(&currentSCN); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I saw sometimes we would use ` not " for SQL queries

}

// StartSession starts a LogMiner session with ONLINE_CATALOG strategy
func (sm *SessionManager) StartSession(ctx context.Context, startSCN, endSCN uint64, committedDataOnly bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to add protection against calling it out of order?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've had a look at this and I don't think it's it's a concern personally. It's only used in the one place and I've simplified it so the call sequence is clearer.

@claude
Copy link

claude bot commented Mar 7, 2026

Claude finished @josephwoodward's task in 2m 41s —— View job


  • Validate commit policy
  • Read project patterns (godev.md, tester.md)
  • Review code changes
  • Post inline comments
  • Post summary

Commits

  1. 35bf7acfd41 oracle_cdc: setting up local test infrastructure — "setting up" is not imperative mood. Should be e.g. oracle_cdc: set up local test infrastructure.
  2. 242bb68d013 oracle_cdc: snapshotting across multiple schemas working — not imperative mood; reads as status update. Should be e.g. oracle_cdc: support snapshotting across multiple schemas.
  3. d9b4974bd6d oracle_cdc: test verifying various types passing — not imperative mood. Should be e.g. oracle_cdc: add test for various column types.
  4. 583887dbdb6 oracle_cdc: clean up and refactorings following code reviews — "refactorings" is a noun, not imperative. Should be e.g. oracle_cdc: clean up and refactor following code reviews.
  5. 85c4a14038f oracle_cdc: snapshot refactorings and tweaks based on feedback — "refactorings" is a noun, not imperative. Should be e.g. oracle_cdc: refactor snapshots and apply review feedback.
  6. d560236fca1 oracle_cdc: export config params following feedbac — message is truncated ("feedbac" instead of "feedback").
  7. 84b1de0b88d oracle_cdc: filter our lob events by default — typo: "our" should be "out".

Review

Well-structured CDC implementation with good architectural separation (LogMiner engine, session manager, transaction buffer, SQL redo parser). Component registration (init() + MustRegisterBatchInput), public wrapper (public/components/oracledb/package.go), bundle imports (all/package.go, cloud/package.go), info.csv entry, and RCL license headers are all correct. Integration tests follow project patterns (integration.CheckSkip, assert.Eventually with mutex, license.InjectTestService, context.Background() in cleanup, no require inside assert.Eventually). The latest commit cleanly removes LOB event filtering but leaves a stale struct field. The documentation metadata mismatch flagged in the previous review (input_oracledb_cdc.go:69-70 documents table_schema/table_name but batcher.go:138-139 sets schema/table) was propagated to the asciidoc in this commit rather than fixed. Previous review issues (ConvertValue column-type bug, time.Sleep backoff, dedicated sql.Conn for LogMiner, stored proc ordering, Operation.Scan int64 handling) have been addressed or discussed.

  1. Bug: Leftover IncludeLOBEvents field in Config struct (config.go:45) — The field was removed from the config spec, parsing, and usage but left in the struct as dead code. See inline comment for details.

This is more complicated than it looks. LOB data is streams in a
separate redo event.
Comment on lines +137 to +138
msg := service.NewMessage(data)
msg.MetaSet("schema", m.Schema)
Copy link

Choose a reason for hiding this comment

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

Bug: The metadata keys set here (schema, table) don't match the documented metadata field names in the component's Description (in input_oracledb_cdc.go). The documentation promises table_schema and table_name:

  • table_schema: Schema of the table that the message originated from.
  • table_name: Name of the table that the message originated from.

Users relying on the documented metadata keys in their Bloblang mappings (e.g., meta("table_schema")) will get empty values. Either the code should use table_schema/table_name, or the documentation should be updated to match schema/table.

Comment on lines +86 to +91
if err := createUpsertStoredProc(ctx, db, cacheTable); err != nil {
_ = db.Close()
return nil, fmt.Errorf("creating checkpoint cache write stored procedure: %w", err)
}

if created, err := createCacheTable(ctx, db, cacheTable); err != nil {
Copy link

Choose a reason for hiding this comment

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

Bug: createUpsertStoredProc is called before createCacheTable, but the stored procedure body contains static SQL referencing the cache table (SELECT COUNT(*) INTO v_count FROM %s ..., UPDATE %s ..., INSERT INTO %s ...). On first run when neither exists, Oracle will create the procedure in INVALID state because the referenced table doesn't exist yet.

Notably, the MSSQL checkpoint cache in this same PR was fixed to swap the order — table creation first, then stored procedure creation. The same ordering fix should be applied here: move createCacheTable before createUpsertStoredProc.

"github.com/redpanda-data/connect/v4/internal/license"
)

func TestIntegration_OracleDBCDC_SnapshotAndStreaming(t *testing.T) {
Copy link

Choose a reason for hiding this comment

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

Test naming convention violation: Test function names should use camelCase, not underscores per project test patterns. For example, TestIntegration_OracleDBCDC_SnapshotAndStreaming should be TestIntegrationOracleDBCDCSnapshotAndStreaming. This applies to all test functions in this file and in snapshot_test.go (e.g., TestIntegration_Snapshot).

@claude
Copy link

claude bot commented Mar 9, 2026

Commits
LGTM

Review
New oracledb_cdc enterprise input component implementing Oracle CDC via LogMiner with snapshotting, checkpoint caching, and batch publishing. Well-structured with proper distribution registration, license headers, and comprehensive integration tests.

  1. Bug: Metadata key mismatchbatcher.go L137-138: Code sets metadata keys schema and table, but the component documentation promises table_schema and table_name. Users relying on the documented keys will get empty values.

  2. Bug: Stored procedure created before tablecheckpoint_cache.go L86-L91: createUpsertStoredProc is called before createCacheTable, but the procedure body references the table via static SQL. On first run, Oracle creates the procedure in INVALID state. The MSSQL checkpoint cache in this same PR was fixed to swap the order — the same fix should be applied here.

  3. Test naming conventionintegration_test.go L32: Test function names use underscores (e.g., TestIntegration_OracleDBCDC_SnapshotAndStreaming) instead of the required camelCase convention per project test patterns.

Comment on lines +427 to +432
require.Truef(t, ok, "message %d missing 'schema' metadata", i)

table, ok := msg.MetaGet("table")
require.Truef(t, ok, "message %d missing 'table' metadata", i)

key := fmt.Sprintf("%s.%s", schema, table)
Copy link

Choose a reason for hiding this comment

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

Bug: Metadata key mismatchmustAssertMetadata reads "schema" and "table" metadata keys, but batchPublisher.Publish (in batcher.go) sets "table_schema" and "table_name".

These keys will never be found, so ok will always be false and the require.Truef will fail when the test actually runs.

Change msg.MetaGet("schema")msg.MetaGet("table_schema") and msg.MetaGet("table")msg.MetaGet("table_name").

@claude
Copy link

claude bot commented Mar 9, 2026

Commits

  1. Commit d560236 (oracle_cdc: export config params following feedbac) — headline is truncated and message body says "oracle_cdc: someting", making it incomprehensible. Should be squashed or reworded.

Review
New oracledb_cdc input component implementing Oracle CDC via LogMiner with snapshotting, transaction caching, checkpoint management, SQL redo parsing, and comprehensive integration tests. Component registration, public wrapper, bundle imports, info.csv metadata, and license headers all follow project conventions.

  1. Bug: Metadata key mismatch in integration testintegration_test.go#L427-L432: mustAssertMetadata checks for "schema" and "table" metadata keys, but batchPublisher.Publish sets "table_schema" and "table_name". The test will fail when actually executed against a container.
  2. Test naming conventionsnapshot_test.go#L48: TestIntegration_Snapshot uses underscore separator. Per project test patterns, test function names should use camelCase (TestIntegrationSnapshot).

@claude
Copy link

claude bot commented Mar 9, 2026

Commits

  1. Commit d560236 ("oracle_cdc: export config params following feedbac") — headline has typo "feedbac" (missing 'k'), and the commit body is "oracle_cdc: someting" which is incomprehensible.
  2. Commit df01a33 ("oracle_cdc: addresss Claude review") — typo "addresss" (triple 's').
  3. Commit 84b1de0 ("oracle_cdc: filter our lob events by default") — typo "our" should be "out".

Review
New oracledb_cdc enterprise input component implementing Oracle CDC via LogMiner with snapshotting, transaction caching, checkpoint caching, and SQL redo log parsing. Component registration, config spec, field constants, license headers, public wrapper, bundle imports, and info.csv entry all follow project patterns correctly. Integration and unit tests are comprehensive.

  1. Snapshot double-close bugprocessSnapshot closes the snapshot on every code path, and the goroutine's defer snapshotter.Close() closes it again. Snapshot.Close() does not nil out s.db, so the second call attempts to close an already-closed *sql.DB. See inline comment.

// https://github.com/redpanda-data/connect/blob/main/licenses/rcl.md

package oracledb

Copy link

Choose a reason for hiding this comment

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

Bug: Missing Oracle database driver import.

This package calls sql.Open("oracle", ...) (here in Connect(), and also in checkpoint_cache.go and snapshot.go), but the go-ora driver is never imported in any production code file. The side-effect import _ "github.com/sijms/go-ora/v2" only appears in test files (integration_test.go and oracledbtest.go). Note that the goora "github.com/sijms/go-ora/v2/network" import in logminer.go imports a sub-package for the OracleError type — it does not trigger the root package's init() that registers the "oracle" driver with database/sql.

At runtime this will fail with sql: unknown driver "oracle".

Add _ "github.com/sijms/go-ora/v2" to the import block in this file (or in the public wrapper public/components/oracledb/package.go).

Comment on lines +140 to +145
}
}
}

// FindStartPos finds the earliest possible SCN that exists within a log that's still available.
func (lm *LogMiner) FindStartPos(ctx context.Context) (replication.SCN, error) {
Copy link

Choose a reason for hiding this comment

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

Context propagation: time.Sleep ignores context cancellation.

Per project Go patterns — "Check for cancellation in long-running loops" using select with ctx.Done().

The default branch sleeps without checking context cancellation. If the context is cancelled during time.Sleep(lm.cfg.MiningBackoffInterval) (default 5s), shutdown is delayed until the sleep completes. Replace the time.Sleep calls with a select on ctx.Done() and a timer, e.g.:

select {
case <-ctx.Done():
    return ctx.Err()
case <-time.After(lm.cfg.MiningBackoffInterval):
}

@claude
Copy link

claude bot commented Mar 9, 2026

Commits

  1. Commit d560236 (oracle_cdc: export config params following feedbac) — message quality violation: headline is truncated ("feedbac"), and the commit body is oracle_cdc: someting which is incomprehensible. This should be squashed or amended with a clear message.

Review
New oracledb_cdc enterprise input component adding Oracle CDC via LogMiner with snapshotting, checkpointing, and batching. Component registration, license headers, bundle imports, info.csv, public wrapper, and test structure all follow project patterns well.

  1. Bug: Missing Oracle driver import — The production code calls sql.Open("oracle", ...) in multiple files but never imports the go-ora driver (_ "github.com/sijms/go-ora/v2"). The import only exists in test files. This will fail at runtime. (comment) — source
  2. Context propagation: time.Sleep ignores cancellationLogMiner.ReadChanges uses time.Sleep in a select default branch without checking ctx.Done(), delaying shutdown by up to 5 seconds. (comment) — source

Comment on lines +59 to +63
for i, t := range userTables {
if i > 0 {
buf.WriteString(" OR ")
}
fmt.Fprintf(&buf, "(SEG_OWNER = '%s' AND TABLE_NAME = '%s')", t.Schema, t.Name)
Copy link

Choose a reason for hiding this comment

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

SQL Injection: Schema and table names from DBA_TABLES are interpolated directly into SQL string literals with single quotes. Oracle quoted identifiers (created with double quotes) can contain single quotes — e.g., CREATE TABLE "it's" (id NUMBER) stores it's in DBA_TABLES. This would produce TABLE_NAME = 'it's', breaking the SQL and enabling injection.

Escape single quotes by doubling them (strings.ReplaceAll(t.Schema, "'", "''")) before interpolation, or use bind variables if the query structure permits it.

@claude
Copy link

claude bot commented Mar 9, 2026

Commits

  1. oracle_cdc: export config params following feedbac — typo in headline ("feedbac" → "feedback"), and the commit body oracle_cdc: someting is incomprehensible.
  2. oracle_cdc: addresss Claude review — typo "addresss" (extra 's').
  3. oracle_cdc: filter our lob events by default — typo "our" should be "out".

Review
New oracledb_cdc input component implementing Oracle CDC via LogMiner with snapshotting, checkpoint caching, and batch publishing. Architecture follows project patterns well — proper license headers, field name constants, init() registration, public wrapper, bundle imports, info.csv entry, enterprise license check, and integration tests with testcontainers-go.

  1. SQL Injection in LogMiner query constructioninternal/impl/oracledb/logminer/logminer.go#L59-L63: Schema and table names from DBA_TABLES are interpolated directly into SQL string literals using single quotes. Oracle quoted identifiers can contain single quotes, making this a SQL injection vector. Per CLAUDE.md security guidelines, SQL injection must be avoided.

Comment on lines +137 to +143
} else {
time.Sleep(lm.cfg.MiningInterval)
}
}
}
}

Copy link

Choose a reason for hiding this comment

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

time.Sleep in this loop blocks context cancellation. When ctx is canceled (e.g., during shutdown), the goroutine won't notice until the sleep completes — up to MiningBackoffInterval (default 5s) in the worst case.

Per the project context propagation pattern, long-running loops should check for cancellation using a select:

select {
case <-ctx.Done():
    return ctx.Err()
case <-time.After(lm.cfg.MiningBackoffInterval):
}

This adds up to shutdownTimeout (5s) of unnecessary shutdown latency since Close() also waits 5s before hard-stopping.

@claude
Copy link

claude bot commented Mar 10, 2026

Commits

  1. oracle_cdc: export config params following feedbac (d560236) — truncated message, appears to be a typo ("feedbac" → "feedback").
  2. oracle_cdc: addresss Claude review (df01a33) — typo in message ("addresss" → "address"). Also vague — doesn't describe the actual change made.
  3. oracle_cdc: address Claude feedback (72c6de3) — vague message that doesn't describe the actual change. Commit messages should summarize what changed, not reference an external review.
  4. oracle_cdc: filter our lob events by default (84b1de0) — typo ("our" → "out").

Review
New oracledb_cdc input component with LogMiner-based CDC, snapshot support, checkpoint caching, and comprehensive integration tests. Component registration, bundle imports, info.csv metadata, license headers, and distribution placement all follow project patterns correctly.

  1. time.Sleep in mining loop blocks context cancellationReadChanges uses time.Sleep in its main loop (logminer.go L137-L143), which blocks context cancellation for up to MiningBackoffInterval (default 5s). Should use a select with ctx.Done() and time.After to remain responsive to shutdown signals, per the context propagation pattern.

@josephwoodward josephwoodward merged commit a0d75ec into main Mar 10, 2026
7 checks passed
@josephwoodward josephwoodward deleted the jw/oracledb_cdc branch March 10, 2026 11:57
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.

2 participants