{"owner":"apache","repo":"skywalking","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md"],"files":{"CLAUDE.md":"# CLAUDE.md - AI Assistant Guide for Apache SkyWalking\n\nThis file provides guidance for AI assistants working with the Apache SkyWalking codebase.\n\n## Project Overview\n\nApache SkyWalking is an open-source APM (Application Performance Monitoring) system designed for microservices, cloud-native, and container-based architectures. It provides distributed tracing, service mesh telemetry analysis, metrics aggregation, alerting, and observability capabilities.\n\n## Repository Structure\n\n```\nskywalking/\n├── oap-server/                    # OAP (Observability Analysis Platform) backend server\n│   ├── server-core/               # Core module with fundamental services\n│   ├── server-library/            # Shared libraries (module system, util, etc.)\n│   ├── server-receiver-plugin/    # Data receivers (gRPC, HTTP, Kafka, etc.)\n│   ├── server-storage-plugin/     # Storage implementations (BanyanDB, Elasticsearch, etc.)\n│   ├── server-cluster-plugin/     # Cluster coordination (Zookeeper, K8s, etc.)\n│   ├── server-query-plugin/       # Query interfaces (GraphQL)\n│   ├── server-alarm-plugin/       # Alerting system\n│   ├── server-fetcher-plugin/     # Data fetchers\n│   ├── server-configuration/      # Dynamic configuration providers\n│   ├── oal-grammar/               # OAL (Observability Analysis Language) grammar\n│   ├── oal-rt/                    # OAL runtime\n│   ├── mqe-grammar/               # MQE (Metrics Query Engine) grammar\n│   ├── mqe-rt/                    # MQE runtime\n│   ├── server-testing/             # Shared test utilities (DSL test framework)\n│   ├── analyzer/                  # Log and trace analyzers\n│   ├── ai-pipeline/               # AI/ML pipeline components\n│   ├── exporter/                  # Data export functionality\n│   └── server-tools/              # Standalone tools (profile exporter) with mock providers\n├── apm-protocol/                  # Protocol definitions (submodule)\n│   └── apm-network/               # gRPC/Protobuf network protocols\n├── skywalking-ui/                 # Web UI (submodule - skywalking-booster-ui)\n├── apm-webapp/                    # Web application packaging\n├── apm-dist/                      # Distribution packaging\n├── docs/                          # Documentation\n├── docker/                        # Docker build files\n├── test/                          # E2E and integration tests\n└── tools/                         # Development tools\n```\n\n## Architecture & Key Concepts\n\n### Module System\nSkyWalking uses a custom module/provider architecture based on Java SPI:\n\n- **ModuleDefine**: Declares a module and its required services\n- **ModuleProvider**: Implements a module with specific technology/approach\n- **Service**: Interface that modules expose to other modules\n\nKey pattern:\n```java\npublic class XxxModule extends ModuleDefine {\n    public Class[] services() {\n        return new Class[] { XxxService.class };\n    }\n}\n\npublic class XxxModuleProvider extends ModuleProvider {\n    public void prepare() { /* initialize */ }\n    public void start() { /* start services */ }\n}\n```\n\n### Core Concepts\n- **OAL (Observability Analysis Language)**: DSL for defining metrics aggregation rules\n- **MQE (Metrics Query Engine)**: DSL for querying metrics\n- **LAL (Log Analysis Language)**: DSL for log processing\n- **MAL (Meter Analysis Language)**: DSL for meter data processing\n- **Source/Scope**: Data model definitions for telemetry data\n- **Stream Processing**: Metrics, Records, and TopN processing pipelines\n\n### Data Flow\n1. Agents/Collectors send data via gRPC/HTTP/Kafka\n2. Receiver plugins parse and validate data\n3. Analysis engine processes data using OAL/LAL/MAL\n4. Storage plugins persist aggregated data\n5. Query plugins serve data to UI/API\n\n## Code Style & Conventions\n\n### Checkstyle Rules (enforced via `apm-checkstyle/checkStyle.xml`)\n\n**Prohibited patterns:**\n- No `System.out.println` - use proper logging (SLF4J)\n- No `@author` tags - ASF projects don't use author annotations\n- No Chinese characters in source files\n- No tab characters (use 4 spaces)\n- No star imports (`import xxx.*`)\n- No unused or redundant imports\n- No empty statements (standalone `;`)\n- No fully-qualified class names inline in code — always add an `import` statement and\n  use the short name. Acceptable exceptions: (a) two classes with the same simple name\n  would collide if both imported, (b) the class appears exactly once in a Javadoc\n  `{@link}` where the short name would be ambiguous to the reader, (c) the name is inside\n  GENERATED source — a codegen string literal or a `.ftl` template. Field declarations,\n  method signatures, local variables, and generic type arguments should always use the\n  imported short name — `private RemoteClientManager rcm;`, not `private\n  org.apache.skywalking.oap.server.core.remote.client.RemoteClientManager rcm;`.\n  - Exception (c) is a Javassist constraint, not a preference: its compiler has no\n    `import` statement and resolves simple names only against `java.lang`\n    (`ClassPool.importedPackages`; widening it needs `importPackage`, which is pool-wide\n    mutable state and is never called here). So generated source must fully qualify\n    everything, `java.util.Map` included. Shortening an FQCN in a codegen string or a\n    template compiles as Java and then fails at runtime — audit real Java only.\n- No one-line delegate methods. A wrapper whose only body is a single forwarding call\n  to another class (`return Other.foo(a, b);`) adds a hop without value. Inline the\n  call at the use site, or call the underlying object directly (including via method\n  reference: `obj::foo` instead of `this::wrapperOfFoo`).\n\n**Required patterns:**\n- `@Override` annotation required for overridden methods\n- `equals()` and `hashCode()` must be overridden together\n- Javadoc `@param`, `@return`, `@throws` must have descriptions\n- Long constants must use uppercase `L` (e.g., `100L` not `100l`)\n- `default` case must come last in switch statements\n- One statement per line\n\n**Naming conventions:**\n- Constants/static variables: `UPPER_CASE_WITH_UNDERSCORES`\n- Type parameters: `UPPER_CASE` (e.g., `TYPE`, `KEY`, `VALUE`)\n- Package names: `org.apache.skywalking.*` or `test.apache.skywalking.*`\n- Type names: `PascalCase` or `UPPER_CASE_WITH_UNDERSCORES`\n- Local variables/parameters/members: `camelCase`\n- **Function-oriented naming, not abstract metaphor**: classes and methods are named for\n  what they do, not for an abstract concept. Prefer concrete verbs (`load`, `apply`,\n  `unregister`, `compile`, `verify`, `commit`, `rollback`) over metaphorical ones\n  (`seed`, `hydrate`, `bootstrap`, `prime`). Class names follow the same rule —\n  `StaticRuleLoader` (loads static rules), not `StaticBundleSeeder`; `DSLSyncTimer` (syncs\n  DB → state on a timer), not `TickRunner`. If you can't name a method without reaching\n  for a metaphor, the method is probably doing too much; split it.\n\n**File limits:**\n- Max file length: 3000 lines\n\n**Whitespace:**\n- Whitespace required after commas, semicolons, type casts\n- Whitespace required around operators\n- No multiple consecutive blank lines\n- Empty line separators between class members (fields can be grouped)\n\n### Code Style (via `codeStyle.xml` for IntelliJ IDEA)\n\n**Indentation:**\n- 4-space indentation\n- 4-space continuation indent\n\n**Imports:**\n- No star imports (threshold set to 999)\n- Import order: regular imports, blank line, static imports\n\n**Formatting:**\n- `while` in do-while on new line\n- Align multiline chained method calls\n- Align multiline parameters in calls\n- Array initializer braces on new lines\n- Wrap long method call chains\n\n**General:**\n- Use `final` for local variables and parameters\n- Use Lombok annotations (`@Getter`, `@Setter`, `@Builder`, `@Data`, `@Slf4j`, etc.)\n- Follow existing patterns in similar files\n\n**Comments:**\n- Don't explain what the code already says. Comment only when reading the code is\n  not enough — invariants that aren't visible in this method, cross-cutting\n  ordering, why a non-obvious branch exists, why a seemingly redundant call is\n  required. If the code is clear, no comment.\n- Connect logic that lives in different places. A check here that exists only\n  because of a guarantee made elsewhere deserves a one-line pointer to that\n  guarantee. A workaround for a known bug elsewhere deserves a link.\n- Trim restated javadoc. Method-level docs should describe the contract, not\n  re-narrate the body. Multi-paragraph rationale belongs in the design doc, not\n  the source.\n\n**Tests:**\n- Always use JUnit assertions (`assertTrue`, `assertEquals`, `assertThrows`, …).\n  Never write bare Java `assert` statements in tests. Although surefire defaults\n  `enableAssertions=true` so bare `assert` does fire in this project's tests,\n  JUnit assertions are the convention everywhere else in the codebase, survive\n  any test-runner configuration, and produce a structured failure that names\n  the actual value instead of a context-free `AssertionError`.\n\n### License Header\nJava, XML, and YAML/YML files must include the Apache 2.0 license header (see `HEADER` file).\nJSON and Markdown files are excluded (JSON doesn't support comments, see `.licenserc.yaml`).\n\n### JDK 11 Compatibility\n\nAll code must be compatible with JDK 11 (LTS). The project supports JDK 11, 17, and 21.\n\n**Prohibited Java features (post-JDK 11):**\n\n| Feature | JDK Version | Use Instead |\n|---------|-------------|-------------|\n| Switch expressions (`->`) | 14+ | Traditional `switch` with `case:` and `break` |\n| `Stream.toList()` | 16+ | `.collect(Collectors.toList())` |\n| Text blocks (`\"\"\"...\"\"\"`) | 15+ | String concatenation or `+` |\n| Records | 14+ | Regular classes with Lombok `@Data` |\n| Pattern matching for `instanceof` | 14+ | Traditional cast after `instanceof` |\n| Sealed classes/interfaces | 15+ | Regular classes/interfaces |\n\n**Allowed Java features (JDK 11 compatible):**\n- `List.of()`, `Set.of()`, `Map.of()` - Immutable collections (Java 9+)\n- `Optional` methods - `orElseThrow()`, `ifPresentOrElse()` (Java 9+)\n- Lambda expressions and method references (Java 8+)\n- Stream API (Java 8+)\n- Lombok annotations (`@Getter`, `@Builder`, `@Data`, `@Slf4j`)\n\n**Verification commands:**\n```bash\n# Check for switch expressions (should return no matches)\ngrep -r \"switch.*->\" src/ --include=\"*.java\"\n\n# Check for Stream.toList() (should return no matches)\ngrep -r \"\\.toList()\" src/ --include=\"*.java\"\n\n# Check for text blocks (should return no matches)\ngrep -r '\"\"\"' src/ --include=\"*.java\"\n```\n\n## Git Submodules\n\nThe project uses submodules for protocol definitions and UI:\n- `apm-protocol/apm-network/src/main/proto` - skywalking-data-collect-protocol\n- `oap-server/server-query-plugin/.../query-protocol` - skywalking-query-protocol\n- `skywalking-ui` - skywalking-booster-ui\n- `oap-server/server-library/library-banyandb-client/src/main/proto` - banyandb-client-proto\n\nAlways use `--recurse-submodules` when cloning or update submodules manually.\n\n## Key Files for Understanding the Codebase\n\n- `oap-server/server-core/src/main/java/.../CoreModule.java` - Core module definition\n- `oap-server/server-library/library-module/src/main/java/.../ModuleDefine.java` - Module system base\n- `oap-server/oal-grammar/src/main/antlr4/.../OALParser.g4` - OAL grammar definition\n- `oap-server/server-starter/` - Application entry point\n- `docs/en/concepts-and-designs/` - Architecture documentation\n\n## Common Development Tasks\n\n### Adding a New Receiver Plugin\n1. Create module in `server-receiver-plugin/`\n2. Implement `ModuleDefine` and `ModuleProvider`\n3. Register via SPI in `META-INF/services/`\n4. Add configuration to `application.yml`\n\n### Adding a New Storage Plugin\n1. Create module in `server-storage-plugin/`\n2. Implement storage DAOs for each data type\n3. Follow existing plugin patterns (e.g., BanyanDB, elasticsearch)\n\n### Modifying OAL Metrics\n1. Edit `.oal` files in `oap-server/server-starter/src/main/resources/oal/`\n2. Regenerate by building the project\n3. Update storage schema if needed\n\n### MAL Scripts (in `oap-server/server-starter/src/main/resources/`)\n- `otel-rules/` - OpenTelemetry metrics (Prometheus, etc.)\n- `meter-analyzer-config/` - SkyWalking native meter protocol\n\n### LAL Scripts (in `oap-server/server-starter/src/main/resources/`)\n- `lal/` - Log processing rules\n- `log-mal-rules/` - Metrics extracted from logs\n\n## Documentation (in `docs/en/`, structure defined in `docs/menu.yml`)\n\n- `concepts-and-designs/` - Architecture and core concepts (OAL, MAL, LAL, profiling)\n- `setup/` - Installation and configuration guides\n- `api/` - Telemetry and query protocol documentation\n- `guides/` - Contributing guides, build instructions, testing\n- `changes/changes.md` - Changelog (update when making changes). Keep each bullet's **prose on one line**\n  — don't hard-wrap a sentence across source lines (nested sub-bullets `  - …` are fine; they're real list\n  items). The changelog becomes the GitHub release body, which GitHub renders as GFM where a single\n  newline continuing a bullet becomes a `<br>`, so prose-wrapped bullets show jagged mid-sentence breaks on\n  the release page (verified on v10.4.0; the docs website reflows and hides the problem). See\n  `guides/How-to-release.md`. Append a new entry at the **end** of its section, never at the top —\n  the section reads in the order the changes landed.\n- `swip/` - SkyWalking Improvement Proposals\n\n### SWIP vs. operator docs\n\nThe two kinds of document answer different questions and have different lifetimes:\n\n- **`docs/en/swip/SWIP-N.md` records the design discussion up to first acceptance.** It is a\n  historical record of what was proposed and agreed, not a description of current behaviour.\n- **The operator / end-user docs describe how the system behaves today.** They are the only\n  place a reader should be sent for current truth.\n\nWhen the implementation of a SWIP lands, sync the SWIP once to what was actually built — every\nproposed change written in implemented/past tense, with **no `TODO` / \"future work\" /\n\"open dependency\" / \"lands later\" / \"empty until X\" language for anything in the SWIP's\nscope**. **Once that SWIP ships as part of a release, it is frozen and must not be edited again.**\n\nFrozen means frozen even when the SWIP's text becomes factually stale. A later refactor that\nrenames a package, moves a class, deletes a constant or changes a class-naming scheme **does not**\nlicense updating the SWIP to match — not to keep code references accurate, not for internal\nconsistency, and not because the same PR already touched it. Ask instead: *would an end user read\nthis to learn how the system works today?* If yes it is an operator doc and must be updated; if it\nis a SWIP the answer is no, and the fix belongs in the operator doc plus `changes.md`.\n\nAfter that first sync, further metrics, dashboards, and incremental enhancements go into the\n**operator doc**, not the SWIP. Only genuinely small, optional follow-ups may stay under a SWIP's\n\"future work\" — never a big change.\n\n- BanyanDB self-observability: the SWIP is `docs/en/swip/SWIP-15.md`; the living operator\n  catalog is `docs/en/banyandb/dashboards-banyandb.md` (menu: \"BanyanDB self observability\n  dashboard\"). New `meter_banyandb_*` rules → update the operator doc; leave the SWIP alone.\n\n### Doc cross-references\n\nReference another section with a **Markdown anchor link** `[section title](#heading-slug)`,\nnever the `§` symbol or a bare \"section N\". Verify every anchor resolves to a real heading\n(GitHub slug rules: lowercase, spaces → `-`, punctuation dropped) — a broken `[](#…)`\nsilently renders as plain text on the website.\n\n## Handling Existing Tests\n\nIf existing test code (UT, IT, or E2E) fails to pass, try to fix it. If any test cases need to be modified, or test code needs to be deleted, the change must be double-checked by a human before it is applied — surface the proposed modification or deletion and the reasoning, and wait for explicit approval rather than changing or removing the test yourself.\n\n## Submitting Pull Requests\n\nUse the `/gh-pull-request` skill for committing and pushing to a PR branch. It runs pre-flight checks (compile, checkstyle, license headers) before every push, and creates the PR if one doesn't exist yet.\n\n## GitHub Actions Allow List\n\nApache enforces an allow list for third-party GitHub Actions. All third-party actions must be pinned to an approved SHA from:\nhttps://github.com/apache/infrastructure-actions/blob/main/approved_patterns.yml\n\nIf a PR is blocked by \"action is not allowed\" errors, check the approved list and update `.github/workflows/` files to use the approved SHA pin instead of a version tag.\n\nActions owned by `actions/*` (GitHub), `github/*`, and `apache/*` are always allowed (enterprise-owned).\n\n## Tips for AI Assistants\n\n1. **Always check submodules**: Protocol changes may require submodule updates\n2. **Generate sources first**: Run `mvnw compile` before analyzing generated code\n3. **Install package**: Use `mvnw flatten:flatten install` to build the precompiler and export generated classes before running tests. ref to [compile skill doc](.claude/skills/compile/SKILL.md)\n3. **Full rebuild on cross-module changes**: If you changed more than two modules or pulled/rebased code from git remote, run `mvnw clean install` (or `mvnw clean package`) on the **whole project** rather than picking individual modules with `-pl`. Incremental `-pl ... -am` builds can leave stale jars in `.m2` or `oap-libs/` when jar sizes don't change but content does, causing hard-to-debug runtime issues.\n3. **Respect checkstyle**: No System.out, no @author, no Chinese characters\n4. **Follow module patterns**: Use existing modules as templates\n5. **Check multiple storage implementations**: Logic may vary by storage type\n6. **OAL generates code**: Don't manually edit generated metrics classes\n7. **Use Lombok**: Prefer annotations over boilerplate code\n8. **Test both unit and integration**: Different test patterns for different scopes\n9. **Documentation is rendered via markdown**: When reviewing docs, consider how they will be rendered by a markdown engine\n10. **Relative paths in docs are valid**: Relative file paths (e.g., `../../../oap-server/...`) in documentation work both in the repo and on the documentation website, supported by website build tooling\n11. **Module service registration**: When adding a service to `CoreModule.services()`, update ALL `CoreModuleProvider` implementations — not just the main one. Search with `grep -rn \"extends CoreModuleProvider\" oap-server/ --include=\"*.java\"`. The `MockCoreModuleProvider` in `server-tools/profile-exporter/` also needs it, or the profile exporter e2e test will fail at startup.\n12. **Multiple OAP packagings**: The OAP server is not only the main `server-starter`. The `server-tools/` directory contains standalone tools (e.g., profile exporter) that boot with mock module providers and a subset of modules. Changes to core module contracts (services, required modules) must be reflected in these tools too.\n13. **`moduleManager.find(X.NAME)` requires `X.NAME` in `requiredModules()`**: every call to `moduleManager.find(SomeModule.NAME)` (direct or through a helper) must have `SomeModule.NAME` in the provider's `requiredModules()` array. Missing declarations cause runtime exceptions the first time the code path fires — not at module boot. Wrapping the call in `try { ... } catch (Throwable)` is NOT a substitute; declare the module and keep the try/catch only for defensive handling of transient provider outages. When auditing a branch, grep for `moduleManager.find(` across the touched module and verify each target name appears in `requiredModules()`. Example modules that frequently catch teams out: `AlarmModule` (used by alarm-kernel reset), `LogAnalyzerModule` (used by LAL factory lookup).\n14. **Don't look up `ClusterModule` services directly**: the `ClusterModule` (ZooKeeper / K8s / Nacos coordination) exposes `ClusterRegister` / `ClusterNodesQuery` / `ClusterCoordinator`. Most receiver / analyzer modules don't declare `ClusterModule` in `requiredModules()`, so calling `moduleManager.find(ClusterModule.NAME)` will throw at runtime. Instead, go through `CoreModule`'s `RemoteClientManager` service — it's already populated by the cluster module and exposes the peer list every OAP needs. If a module genuinely needs cluster-coordinator primitives, declare `ClusterModule.NAME` in `requiredModules()` explicitly.\n15. **No `ThreadLocal` side-channels to hijack downstream behaviour**: routing a caller's intent through a `ThreadLocal` that downstream code reads (e.g., `if (PeerMode.isActive()) skipSomething()`) is almost always the wrong answer — it creates invisible coupling between far-apart code paths, leaks across async hand-offs (executors, gRPC threads, Armeria event loops), and makes the behaviour impossible to understand from a method signature. The correct fix is almost always to **extend the interface** — add a parameter, a new method, a new mode enum that appears in the signature. Rare exceptions: propagating OpenTelemetry context where the whole industry has standardised on `ThreadLocal`, or security principals enforced by a framework. In all other cases, prefer an explicit API extension, even if it costs more lines.\n16. **BanyanDB schema-visibility: fence on `mod_revision`, do NOT poll metadata**: schema no longer lives in etcd — etcd was removed. It is now stored as property documents in BanyanDB's own `_schema` store and propagated to each data node's in-memory cache **asynchronously** (a `SchemaUpdateService.WatchSchemas` gRPC stream + a 30s reconcile sync, see `banyand/metadata/schema/property/client.go`). DDL `Create` / `Update` / `Delete` broadcast only to the schema-server (`ROLE_META`) nodes and **return immediately with a `mod_revision`** — now a client-stamped `time.Now().UnixNano()` timestamp, NOT an etcd index (which is why it still changes on every content-changing DDL; it is `0` on a delete that didn't record a tombstone) — **without waiting for data nodes**. A data write whose schema hasn't propagated yet is dropped at the data-node executor (`cannot find measure definition`, logged + skipped, `banyand/measure/write_standalone.go`), so the fence is still required — arguably more than under the old etcd-watch model. After firing DDL, fence on `BanyanDBClient.getSchemaWatcher().awaitRevisionApplied(maxRev, timeout)` before unparking dispatch / firing data writes — this blocks (liaison-side, all alive nodes, bounded by the timeout) until every data node's `notifiedModRevision` watermark reaches `maxRev`, which the registry's read-after-write does not guarantee. For deletes that returned `mod_revision == 0`, fall back to `awaitSchemaDeleted(SchemaKey, timeout)`. The previous \"poll `findMeasure` until you can read your own write\" idiom predates the `SchemaBarrierService` proto and has been replaced — do not reintroduce it. The `SchemaWatcher` class is in OAP's **in-tree** `library-banyandb-client` (`org.apache.skywalking.library.banyandb.v1.client.SchemaWatcher`); the standalone `skywalking-banyandb-java-client` repo does NOT have these symbols, so don't conclude the API is gone from grepping it. JDBC and ES are synchronous-DDL on the coordinator so they don't need a fence.\n\n## Analysis and Design Principles\n\n**Never guess or speculate.** All analysis must be grounded in source code, documentation, or verified behavior.\n\n### Before making claims\n- **Read the source code** — don't assume how a feature works based on naming or convention. Check the actual implementation.\n- **Read the documentation** — check `docs/en/`, CLAUDE.md files in submodules, and README files before proposing designs.\n- **Check configuration and flags** — verify what flags/env vars exist, their default values, and how they are parsed (e.g., BanyanDB uses viper with `BYDB_` prefix to auto-bind flags to env vars).\n- **Check dependent projects** — SkyWalking depends on BanyanDB, infra-e2e, Helm charts, etc. Read their source code and docs before assuming capabilities (e.g., check Helm chart `values.yaml` for supported fields, check infra-e2e for supported config options). For `skywalking-*` projects, ask the developer if they have the source code locally — searching a local clone is much faster than fetching files via GitHub API.\n\n### Before proposing changes\n- **Verify locally first** — run the code, start the container, execute the test before pushing to CI. Don't use CI as a trial-and-error environment.\n- **Validate file paths and directory structures** — check where data actually goes (e.g., BanyanDB `--stream-root-path /tmp` creates `/tmp/stream/`, `--access-log-root-path /tmp` creates `/tmp/accesslog/`). Don't assume directory names.\n- **Validate YAML syntax** — after editing YAML files (especially with sed/awk), validate with a YAML parser before committing. Corrupted YAML causes silent failures in CI.\n- **Check the actual Docker image** — verify what's available in the container (binaries, shell, directories) before writing commands that depend on them.\n\n### When uncertain\n- **Say \"I don't know\" and investigate** — reading the code is always better than guessing. Use grep, find, and read tools to locate the answer.\n- **Ask the developer first** — if you can't find the source code, don't know how to run something, or the code doesn't make the answer clear, ask the developer where to find it rather than speculate.\n- **Test with real data** — when investigating runtime behavior (e.g., what model names an API returns, what directory structure BanyanDB creates), set up a local test and observe the actual output.\n\n### Docker images\n- **Apache SkyWalking projects** — images are on `ghcr.io/apache/` (e.g., `ghcr.io/apache/skywalking-banyandb:${COMMIT_SHA}`). Tags are full commit SHAs, not short SHAs or version tags.\n- **Official and 3rd-party images** — on Docker Hub (e.g., `ollama/ollama`, `otel/opentelemetry-collector`, `envoyproxy/gateway`).\n- **Always verify the image exists** — `docker pull` before writing CI or e2e configs. Image tags depend on CI publish workflows completing successfully.\n"}}