[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6BTHV9M65YMX558C22H638A":3,"skill-same:01M6BTHV9M65YMX558C22H638A":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":37,"linkOnly":38,"markdownContent":39,"markdownBody":40,"frontMatter":41,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":6,"ownerHtmlUrl":51,"ownerBlogUrl":52,"ownerDescription":53,"updatedAt":54,"install":55,"resources":67},"01M6BTHV9M65YMX558C22H638A","pi-loop",null,"Set up and manage recurring Pi tasks that run as background Pi processes. Use for recurring prompts, scheduled reports, or \u002Floop requests.","ahmedelgabri\u002Fdotfiles","ahmedelgabri","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F63876?v=4","01M6BTGT61KWRDXN595X7Q8ZRY","Ahmed El Gabri","01M6BTGTAFSBR3A7ZFXK5QDHVZ",291,"Unlicense","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fpi\u002Fagent\u002Fextensions\u002Floop\u002Fskills\u002Fpi-loop\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36],"pi loop","loop","pi","prompts","background","for","scheduled","requests","processes","as","manage","reports","tasks","up","use","recurring","SET","run","2026-11-10 10:12:30",false,"---\nname: run-e2e\ndescription: Run SkyWalking E2E tests locally\ndisable-model-invocation: true\nargument-hint: \"[test-case-path]\"\n---\n\n# Run SkyWalking E2E Test\n\nRun an E2E test case using `skywalking-infra-e2e`. The user provides a test case path (e.g., `simple\u002Fjdk`, `storage\u002Fbanyandb`, `alarm`).\n\n## Prerequisites\n\nAll tools require **Go** installed. Check `.github\u002Fworkflows\u002F` for the exact `e2e` commit used in CI.\n\n### e2e CLI\n\nBuilt from [apache\u002Fskywalking-infra-e2e](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-infra-e2e), pinned by commit in CI:\n\n```bash\n# Install the pinned commit\ngo install github.com\u002Fapache\u002Fskywalking-infra-e2e\u002Fcmd\u002Fe2e@\u003Ccommit-id>\n\n# Or clone and build locally (useful when debugging the e2e tool itself)\ngit clone https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-infra-e2e.git\ncd skywalking-infra-e2e\ngit checkout \u003Ccommit-id>\nmake build\n# binary is in bin\u002Fe2e — add to PATH or copy to $GOPATH\u002Fbin\n```\n\n### swctl, yq, and other tools\n\nE2E test cases run pre-install steps (see `setup.steps` in each `e2e.yaml`) that install tools into `\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin`. When running locally, you need these tools on your PATH.\n\n**swctl** — SkyWalking CLI, used in verify cases to query OAP's GraphQL API. Pinned at `SW_CTL_COMMIT` in `test\u002Fe2e-v2\u002Fscript\u002Fenv`:\n\n```bash\n# Option 1: Use the install script (same as CI)\nbash test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002Finstall.sh swctl\nexport PATH=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin:$PATH\n\n# Option 2: Build from source\ngo install github.com\u002Fapache\u002Fskywalking-cli\u002Fcmd\u002Fswctl@\u003CSW_CTL_COMMIT>\n```\n\n**yq** — YAML processor, used in verify cases:\n\n```bash\n# Option 1: Use the install script\nbash test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002Finstall.sh yq\nexport PATH=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin:$PATH\n\n# Option 2: brew install yq (macOS)\n```\n\n**Other tools** (only needed for specific test cases):\n\n| Tool | Install script | Used by |\n|------|---------------|---------|\n| `kubectl` | `install.sh kubectl` | Kubernetes-based tests |\n| `helm` | `install.sh helm` | Helm chart tests |\n| `istioctl` | `install.sh istioctl` | Istio\u002Fservice mesh tests |\n| `etcdctl` | `install.sh etcdctl` | etcd cluster tests |\n\nAll install scripts are at `test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002F`.\n\n## Steps\n\n### 1. Determine the test case\n\nResolve the user's argument to a full path under `test\u002Fe2e-v2\u002Fcases\u002F`. If ambiguous, list matching directories and ask.\n\n```bash\nls test\u002Fe2e-v2\u002Fcases\u002F\u003Cargument>\u002Fe2e.yaml\n```\n\n### 2. Check if rebuild is needed\n\nCompare source file timestamps against the last build:\n\n```bash\n# OAP server changes since last build\nfind oap-server apm-protocol -type f \\( \\\n  -name \"*.java\" -o -name \"*.yaml\" -o -name \"*.yml\" -o \\\n  -name \"*.json\" -o -name \"*.xml\" -o -name \"*.properties\" -o \\\n  -name \"*.proto\" \\\n\\) -newer dist\u002Fapache-skywalking-apm-bin.tar.gz 2>\u002Fdev\u002Fnull | head -5\n\n# Test service changes since last build\nfind test\u002Fe2e-v2\u002Fjava-test-service -type f \\( \\\n  -name \"*.java\" -o -name \"*.xml\" -o -name \"*.yaml\" -o -name \"*.yml\" \\\n\\) -newer test\u002Fe2e-v2\u002Fjava-test-service\u002Fe2e-service-provider\u002Ftarget\u002F*.jar 2>\u002Fdev\u002Fnull | head -5\n```\n\nIf files are found, warn the user and suggest rebuilding before running.\n\n### 3. Rebuild if needed (only with user confirmation)\n\n```bash\n# Rebuild OAP\n.\u002Fmvnw clean flatten:flatten package -Pall -Dmaven.test.skip && make docker\n\n# Rebuild test services\n.\u002Fmvnw -f test\u002Fe2e-v2\u002Fjava-test-service\u002Fpom.xml clean flatten:flatten package\n```\n\n### 4. Run the E2E test\n\nSet required environment variables and run:\n\n```bash\nexport SW_AGENT_JDK_VERSION=8\ne2e run -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n```\n\n### 5. If the test fails\n\nDo NOT run cleanup immediately. Instead:\n\n1. Check container logs:\n   ```bash\n   docker compose -f test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fdocker-compose.yml logs oap\n   docker compose -f test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fdocker-compose.yml logs provider\n   ```\n\n2. Run verify separately (can retry after investigation):\n   ```bash\n   e2e verify -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n   ```\n\n3. Only cleanup when done debugging:\n   ```bash\n   e2e cleanup -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n   ```\n\n### 6. Manually fire each verify query (fast triage)\n\nThe `e2e verify` retry loop runs in sequence and stops at the first failing case, so a single bad query hides every case after it. When a verify fails, **run each verify case directly against the still-running OAP** before editing anything — you'll see the real error (bad flag, missing data, wrong expected), not the progress spinner. This is also the right way to author new verify cases: craft the query against live OAP, confirm the actual YAML, then write the expected file.\n\n```bash\n# Find the host-side port that infra-e2e bound to OAP's container port 12800.\n# (Each run picks a new random port; the trigger log prints it too.)\ndocker ps --filter \"name=skywalking_e2e-oap\" --format \"{{.Ports}}\" \\\n  | grep -oE \"[0-9]+->12800\" | head -1\n# => e.g. 56381->12800\n\nURL=http:\u002F\u002Flocalhost:56381\u002Fgraphql\nSWCTL=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin\u002Fswctl\n\n# Copy the query from e2e.yaml verbatim, then substitute ${oap_host} → localhost\n# and ${oap_12800} → the port you just found:\n$SWCTL --display yaml --base-url=$URL service ly IOS\n$SWCTL --display yaml --base-url=$URL logs list --service-name=MyiOSApp\n$SWCTL --display yaml --base-url=$URL metrics exec --expression=service_cpm --service-name=MyiOSApp\n```\n\nWhen a `swctl` subcommand rejects a flag (`Incorrect Usage: flag provided but not defined: -layer`), the e2e config is using syntax the pinned `swctl` commit doesn't support. Find the right syntax with `swctl \u003Ccmd> --help` and update the e2e config. Common cases encountered:\n\n| Broken flag\u002Fform | Working form |\n|---|---|\n| `service ls --layer IOS` | `service ly IOS` |\n| `metrics exec ... --is-normal=true` | drop `--is-normal` (default behavior) |\n\nFor queries that *don't* use `swctl` (raw `curl` against `\u002Floki\u002F...`, Zipkin, PromQL), hit the matching exposed port:\n\n```bash\ncurl \"http:\u002F\u002Flocalhost:$(docker ps --filter name=skywalking_e2e-oap --format '{{.Ports}}' | grep -oE '[0-9]+->3100' | head -1 | cut -d'-' -f1)\u002Floki\u002Fapi\u002Fv1\u002Flabels\"\n```\n\n### 7. UI template changes require a fresh DB\n\n`UITemplateInitializer.initTemplate()` (in `oap-server\u002Fserver-core`) calls `uiTemplateManagementService.addIfNotExist(setting)` — keyed by the `id` field in each `ui-initialized-templates\u002F**\u002F*.json`. Same ID → skipped. So edits to an *existing* template JSON (adding widgets, relabeling, changing expressions) will **not** be applied on an already-initialized OAP, even after a container restart, because the old copy still lives in storage.\n\nTo pick up dashboard JSON changes:\n\n```bash\n# Remove both containers — BanyanDB stores state inside the container FS in the\n# e2e compose (no named volume), so removing the container wipes state cleanly.\ndocker rm -f skywalking_e2e-oap-1 skywalking_e2e-banyandb-1\n\n# For compose setups that use a named volume, also:\n# docker volume rm \u003Cvolume-name>\n\n# Then re-run — OAP sees empty storage, loads the new template JSON.\ne2e run -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase>\u002Fe2e.yaml\n```\n\nSymptom to watch for: you edit the JSON, rebuild, redeploy — dashboard in the UI still shows the pre-edit layout. That's not a caching bug; that's `addIfNotExist` doing exactly what its name says.\n\n### 8. Author the expected YAML from live output\n\nFor a new verify case, the workflow is:\n\n1. Fire the query manually (see step 6) and capture the YAML.\n2. Pick which fields are *meaningful domain values* (must match exactly) vs *dynamic runtime values* (`notEmpty` \u002F `gt` \u002F `ge`). See `test\u002Fe2e-v2\u002FCLAUDE.md` for the decision guide.\n3. Write the expected file. If the response is a list, wrap the items in `{{- contains . }} ... {{- end }}` so ordering and extra actual items don't fail the match.\n4. Re-run `e2e verify` alone (the containers are still up from the previous run); iterate on the expected file without rebuilding.\n\n### 9. Expected-file authoring traps\n\nThese burn CI cycles and pass locally. Each was learned the hard way.\n\n**Unquoted `content: {{ notEmpty .content }}` with `:` inside the value.** Sim-generated or real log content routinely includes colons (`POST https:\u002F\u002Fapi.example.com\u002Fcart failed: 500`, `HTTP\u002F1.1 500: Internal Server Error`). Without quoting, the template renders to invalid YAML (snakeyaml parses `failed:` as a nested key) and the *whole log entry* marshals to `nil`. Symptom: diff shows `- nil` at *every* position in the expected logs list vs real maps in actual. Fix: wrap in single quotes — `content: '{{ notEmpty .content }}'`. Single-quoted YAML preserves `:` in the scalar; only fails on embedded `'`. Double quotes also work unless the content has `\"`.\n\n**Nested `contains` with multiple per-element pattern assertions against a varied stream.** The template renders the block body once per actual element; when the outer block body has *multiple* inner `contains` patterns asserting specific tag key\u002Fvalue pairs, and only *some* actuals satisfy all the inner patterns, `go-cmp` with `contains` can end up comparing `[rendered_for_A0, nil, nil, ...]` vs `[A0, A1, A2, ...]` and fail despite `contains` being permissive on extras. Specifically: outer `contains .logs` with a single log pattern + inner `contains .tags` asserting two distinct key\u002Fvalue pairs. On a simulator emitting heterogeneous errors (js + promise + ajax + pageNotFound), only a subset satisfy the inner assertion. Passes locally with 1–2 logs, fails in CI with 6+.\n- Keep the outer `contains` body lenient: field-shape checks (`notEmpty`, `gt`), one discriminator tag that *every* element in the stream carries.\n- Cover per-category assertions via separate labeled-metric verify cases, not inside the nested template.\n- Rule of thumb: \"at least one log exists with the right layer routing\" inside the logs expected; per-category coverage via `meter_*_count{label=X}` verify cases.\n\n**Hand-crafted OTLP curl payloads drift from real SDK output.** When the upstream SDK ships a published simulator image (mini-program-monitor's `sim-wechat` \u002F `sim-alipay`, browser-client-js sim, etc.), prefer driving the e2e with that image in `MODE=timed` with a bounded `DURATION_MS` over hand-rolling the OTLP JSON. Hand-crafted payloads miss real-world shape issues: delta-vs-cumulative temporality, label-cardinality surprises, stacktrace formatting variance, attribute key names that changed between SDK versions. Pin to a released tag (`v0.4.0`), not `:latest` or HEAD SHA — reproducibility.\n\n**`timeUnixNano: \"0\"` in an OTLP metric datapoint.** The receiver propagates this into MAL's bucket computation and the metric lands in the 1970 time bucket — `swctl metrics exec` over the \"last N minutes\" window won't find it. Either use `$(date +%s)000000000` at setup time or omit the field if the receiver accepts \"now\" as default.\n\n**Setup-step curl loop with `|| sleep` pattern.** The shell line `for ... do curl && break || sleep 5; done` exits 0 when every attempt connection-refused because the final `sleep 5` returns 0. OAP takes ~50 s to start in CI, so all attempts fail before OAP is ready, and the setup step silently succeeds with zero traffic ingested. Fix: `curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 ...` + `set -e` at step top.\n\n**`swctl` flag rejected.** If a verify case uses a flag the pinned `swctl` commit doesn't support (`service ls --layer` vs `service ly`), the whole case fails 20× before CI gives up. Fire each verify query by hand once before pushing (step 6 above).\n\n**Published image cache miss in CI.** `docker compose pull` sometimes hits rate limits or unreachable registries; the test spins until timeout with \"dependency failed to start\". Look at the CI log for `Error response from daemon: pull access denied` or `manifest unknown`. If you see that, pin a different image tag that's definitely published (check `docker manifest inspect \u003Ctag>` locally), not a floating one.\n\n## Common test cases\n\n| Shorthand | Path |\n|-----------|------|\n| `simple\u002Fjdk` | `test\u002Fe2e-v2\u002Fcases\u002Fsimple\u002Fjdk\u002F` |\n| `storage\u002Fbanyandb` | `test\u002Fe2e-v2\u002Fcases\u002Fstorage\u002Fbanyandb\u002F` |\n| `storage\u002Felasticsearch` | `test\u002Fe2e-v2\u002Fcases\u002Fstorage\u002Felasticsearch\u002F` |\n| `alarm` | `test\u002Fe2e-v2\u002Fcases\u002Falarm\u002F` |\n| `log` | `test\u002Fe2e-v2\u002Fcases\u002Flog\u002F` |\n| `profiling\u002Ftrace` | `test\u002Fe2e-v2\u002Fcases\u002Fprofiling\u002Ftrace\u002F` |\n","# Run SkyWalking E2E Test\n\nRun an E2E test case using `skywalking-infra-e2e`. The user provides a test case path (e.g., `simple\u002Fjdk`, `storage\u002Fbanyandb`, `alarm`).\n\n## Prerequisites\n\nAll tools require **Go** installed. Check `.github\u002Fworkflows\u002F` for the exact `e2e` commit used in CI.\n\n### e2e CLI\n\nBuilt from [apache\u002Fskywalking-infra-e2e](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-infra-e2e), pinned by commit in CI:\n\n```bash\n# Install the pinned commit\ngo install github.com\u002Fapache\u002Fskywalking-infra-e2e\u002Fcmd\u002Fe2e@\u003Ccommit-id>\n\n# Or clone and build locally (useful when debugging the e2e tool itself)\ngit clone https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-infra-e2e.git\ncd skywalking-infra-e2e\ngit checkout \u003Ccommit-id>\nmake build\n# binary is in bin\u002Fe2e — add to PATH or copy to $GOPATH\u002Fbin\n```\n\n### swctl, yq, and other tools\n\nE2E test cases run pre-install steps (see `setup.steps` in each `e2e.yaml`) that install tools into `\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin`. When running locally, you need these tools on your PATH.\n\n**swctl** — SkyWalking CLI, used in verify cases to query OAP's GraphQL API. Pinned at `SW_CTL_COMMIT` in `test\u002Fe2e-v2\u002Fscript\u002Fenv`:\n\n```bash\n# Option 1: Use the install script (same as CI)\nbash test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002Finstall.sh swctl\nexport PATH=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin:$PATH\n\n# Option 2: Build from source\ngo install github.com\u002Fapache\u002Fskywalking-cli\u002Fcmd\u002Fswctl@\u003CSW_CTL_COMMIT>\n```\n\n**yq** — YAML processor, used in verify cases:\n\n```bash\n# Option 1: Use the install script\nbash test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002Finstall.sh yq\nexport PATH=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin:$PATH\n\n# Option 2: brew install yq (macOS)\n```\n\n**Other tools** (only needed for specific test cases):\n\n| Tool | Install script | Used by |\n|------|---------------|---------|\n| `kubectl` | `install.sh kubectl` | Kubernetes-based tests |\n| `helm` | `install.sh helm` | Helm chart tests |\n| `istioctl` | `install.sh istioctl` | Istio\u002Fservice mesh tests |\n| `etcdctl` | `install.sh etcdctl` | etcd cluster tests |\n\nAll install scripts are at `test\u002Fe2e-v2\u002Fscript\u002Fprepare\u002Fsetup-e2e-shell\u002F`.\n\n## Steps\n\n### 1. Determine the test case\n\nResolve the user's argument to a full path under `test\u002Fe2e-v2\u002Fcases\u002F`. If ambiguous, list matching directories and ask.\n\n```bash\nls test\u002Fe2e-v2\u002Fcases\u002F\u003Cargument>\u002Fe2e.yaml\n```\n\n### 2. Check if rebuild is needed\n\nCompare source file timestamps against the last build:\n\n```bash\n# OAP server changes since last build\nfind oap-server apm-protocol -type f \\( \\\n  -name \"*.java\" -o -name \"*.yaml\" -o -name \"*.yml\" -o \\\n  -name \"*.json\" -o -name \"*.xml\" -o -name \"*.properties\" -o \\\n  -name \"*.proto\" \\\n\\) -newer dist\u002Fapache-skywalking-apm-bin.tar.gz 2>\u002Fdev\u002Fnull | head -5\n\n# Test service changes since last build\nfind test\u002Fe2e-v2\u002Fjava-test-service -type f \\( \\\n  -name \"*.java\" -o -name \"*.xml\" -o -name \"*.yaml\" -o -name \"*.yml\" \\\n\\) -newer test\u002Fe2e-v2\u002Fjava-test-service\u002Fe2e-service-provider\u002Ftarget\u002F*.jar 2>\u002Fdev\u002Fnull | head -5\n```\n\nIf files are found, warn the user and suggest rebuilding before running.\n\n### 3. Rebuild if needed (only with user confirmation)\n\n```bash\n# Rebuild OAP\n.\u002Fmvnw clean flatten:flatten package -Pall -Dmaven.test.skip && make docker\n\n# Rebuild test services\n.\u002Fmvnw -f test\u002Fe2e-v2\u002Fjava-test-service\u002Fpom.xml clean flatten:flatten package\n```\n\n### 4. Run the E2E test\n\nSet required environment variables and run:\n\n```bash\nexport SW_AGENT_JDK_VERSION=8\ne2e run -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n```\n\n### 5. If the test fails\n\nDo NOT run cleanup immediately. Instead:\n\n1. Check container logs:\n   ```bash\n   docker compose -f test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fdocker-compose.yml logs oap\n   docker compose -f test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fdocker-compose.yml logs provider\n   ```\n\n2. Run verify separately (can retry after investigation):\n   ```bash\n   e2e verify -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n   ```\n\n3. Only cleanup when done debugging:\n   ```bash\n   e2e cleanup -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase-path>\u002Fe2e.yaml\n   ```\n\n### 6. Manually fire each verify query (fast triage)\n\nThe `e2e verify` retry loop runs in sequence and stops at the first failing case, so a single bad query hides every case after it. When a verify fails, **run each verify case directly against the still-running OAP** before editing anything — you'll see the real error (bad flag, missing data, wrong expected), not the progress spinner. This is also the right way to author new verify cases: craft the query against live OAP, confirm the actual YAML, then write the expected file.\n\n```bash\n# Find the host-side port that infra-e2e bound to OAP's container port 12800.\n# (Each run picks a new random port; the trigger log prints it too.)\ndocker ps --filter \"name=skywalking_e2e-oap\" --format \"{{.Ports}}\" \\\n  | grep -oE \"[0-9]+->12800\" | head -1\n# => e.g. 56381->12800\n\nURL=http:\u002F\u002Flocalhost:56381\u002Fgraphql\nSWCTL=\u002Ftmp\u002Fskywalking-infra-e2e\u002Fbin\u002Fswctl\n\n# Copy the query from e2e.yaml verbatim, then substitute ${oap_host} → localhost\n# and ${oap_12800} → the port you just found:\n$SWCTL --display yaml --base-url=$URL service ly IOS\n$SWCTL --display yaml --base-url=$URL logs list --service-name=MyiOSApp\n$SWCTL --display yaml --base-url=$URL metrics exec --expression=service_cpm --service-name=MyiOSApp\n```\n\nWhen a `swctl` subcommand rejects a flag (`Incorrect Usage: flag provided but not defined: -layer`), the e2e config is using syntax the pinned `swctl` commit doesn't support. Find the right syntax with `swctl \u003Ccmd> --help` and update the e2e config. Common cases encountered:\n\n| Broken flag\u002Fform | Working form |\n|---|---|\n| `service ls --layer IOS` | `service ly IOS` |\n| `metrics exec ... --is-normal=true` | drop `--is-normal` (default behavior) |\n\nFor queries that *don't* use `swctl` (raw `curl` against `\u002Floki\u002F...`, Zipkin, PromQL), hit the matching exposed port:\n\n```bash\ncurl \"http:\u002F\u002Flocalhost:$(docker ps --filter name=skywalking_e2e-oap --format '{{.Ports}}' | grep -oE '[0-9]+->3100' | head -1 | cut -d'-' -f1)\u002Floki\u002Fapi\u002Fv1\u002Flabels\"\n```\n\n### 7. UI template changes require a fresh DB\n\n`UITemplateInitializer.initTemplate()` (in `oap-server\u002Fserver-core`) calls `uiTemplateManagementService.addIfNotExist(setting)` — keyed by the `id` field in each `ui-initialized-templates\u002F**\u002F*.json`. Same ID → skipped. So edits to an *existing* template JSON (adding widgets, relabeling, changing expressions) will **not** be applied on an already-initialized OAP, even after a container restart, because the old copy still lives in storage.\n\nTo pick up dashboard JSON changes:\n\n```bash\n# Remove both containers — BanyanDB stores state inside the container FS in the\n# e2e compose (no named volume), so removing the container wipes state cleanly.\ndocker rm -f skywalking_e2e-oap-1 skywalking_e2e-banyandb-1\n\n# For compose setups that use a named volume, also:\n# docker volume rm \u003Cvolume-name>\n\n# Then re-run — OAP sees empty storage, loads the new template JSON.\ne2e run -c test\u002Fe2e-v2\u002Fcases\u002F\u003Ccase>\u002Fe2e.yaml\n```\n\nSymptom to watch for: you edit the JSON, rebuild, redeploy — dashboard in the UI still shows the pre-edit layout. That's not a caching bug; that's `addIfNotExist` doing exactly what its name says.\n\n### 8. Author the expected YAML from live output\n\nFor a new verify case, the workflow is:\n\n1. Fire the query manually (see step 6) and capture the YAML.\n2. Pick which fields are *meaningful domain values* (must match exactly) vs *dynamic runtime values* (`notEmpty` \u002F `gt` \u002F `ge`). See `test\u002Fe2e-v2\u002FCLAUDE.md` for the decision guide.\n3. Write the expected file. If the response is a list, wrap the items in `{{- contains . }} ... {{- end }}` so ordering and extra actual items don't fail the match.\n4. Re-run `e2e verify` alone (the containers are still up from the previous run); iterate on the expected file without rebuilding.\n\n### 9. Expected-file authoring traps\n\nThese burn CI cycles and pass locally. Each was learned the hard way.\n\n**Unquoted `content: {{ notEmpty .content }}` with `:` inside the value.** Sim-generated or real log content routinely includes colons (`POST https:\u002F\u002Fapi.example.com\u002Fcart failed: 500`, `HTTP\u002F1.1 500: Internal Server Error`). Without quoting, the template renders to invalid YAML (snakeyaml parses `failed:` as a nested key) and the *whole log entry* marshals to `nil`. Symptom: diff shows `- nil` at *every* position in the expected logs list vs real maps in actual. Fix: wrap in single quotes — `content: '{{ notEmpty .content }}'`. Single-quoted YAML preserves `:` in the scalar; only fails on embedded `'`. Double quotes also work unless the content has `\"`.\n\n**Nested `contains` with multiple per-element pattern assertions against a varied stream.** The template renders the block body once per actual element; when the outer block body has *multiple* inner `contains` patterns asserting specific tag key\u002Fvalue pairs, and only *some* actuals satisfy all the inner patterns, `go-cmp` with `contains` can end up comparing `[rendered_for_A0, nil, nil, ...]` vs `[A0, A1, A2, ...]` and fail despite `contains` being permissive on extras. Specifically: outer `contains .logs` with a single log pattern + inner `contains .tags` asserting two distinct key\u002Fvalue pairs. On a simulator emitting heterogeneous errors (js + promise + ajax + pageNotFound), only a subset satisfy the inner assertion. Passes locally with 1–2 logs, fails in CI with 6+.\n- Keep the outer `contains` body lenient: field-shape checks (`notEmpty`, `gt`), one discriminator tag that *every* element in the stream carries.\n- Cover per-category assertions via separate labeled-metric verify cases, not inside the nested template.\n- Rule of thumb: \"at least one log exists with the right layer routing\" inside the logs expected; per-category coverage via `meter_*_count{label=X}` verify cases.\n\n**Hand-crafted OTLP curl payloads drift from real SDK output.** When the upstream SDK ships a published simulator image (mini-program-monitor's `sim-wechat` \u002F `sim-alipay`, browser-client-js sim, etc.), prefer driving the e2e with that image in `MODE=timed` with a bounded `DURATION_MS` over hand-rolling the OTLP JSON. Hand-crafted payloads miss real-world shape issues: delta-vs-cumulative temporality, label-cardinality surprises, stacktrace formatting variance, attribute key names that changed between SDK versions. Pin to a released tag (`v0.4.0`), not `:latest` or HEAD SHA — reproducibility.\n\n**`timeUnixNano: \"0\"` in an OTLP metric datapoint.** The receiver propagates this into MAL's bucket computation and the metric lands in the 1970 time bucket — `swctl metrics exec` over the \"last N minutes\" window won't find it. Either use `$(date +%s)000000000` at setup time or omit the field if the receiver accepts \"now\" as default.\n\n**Setup-step curl loop with `|| sleep` pattern.** The shell line `for ... do curl && break || sleep 5; done` exits 0 when every attempt connection-refused because the final `sleep 5` returns 0. OAP takes ~50 s to start in CI, so all attempts fail before OAP is ready, and the setup step silently succeeds with zero traffic ingested. Fix: `curl -sS -f --retry 30 --retry-delay 5 --retry-connrefused --retry-all-errors --max-time 10 ...` + `set -e` at step top.\n\n**`swctl` flag rejected.** If a verify case uses a flag the pinned `swctl` commit doesn't support (`service ls --layer` vs `service ly`), the whole case fails 20× before CI gives up. Fire each verify query by hand once before pushing (step 6 above).\n\n**Published image cache miss in CI.** `docker compose pull` sometimes hits rate limits or unreachable registries; the test spins until timeout with \"dependency failed to start\". Look at the CI log for `Error response from daemon: pull access denied` or `manifest unknown`. If you see that, pin a different image tag that's definitely published (check `docker manifest inspect \u003Ctag>` locally), not a floating one.\n\n## Common test cases\n\n| Shorthand | Path |\n|-----------|------|\n| `simple\u002Fjdk` | `test\u002Fe2e-v2\u002Fcases\u002Fsimple\u002Fjdk\u002F` |\n| `storage\u002Fbanyandb` | `test\u002Fe2e-v2\u002Fcases\u002Fstorage\u002Fbanyandb\u002F` |\n| `storage\u002Felasticsearch` | `test\u002Fe2e-v2\u002Fcases\u002Fstorage\u002Felasticsearch\u002F` |\n| `alarm` | `test\u002Fe2e-v2\u002Fcases\u002Falarm\u002F` |\n| `log` | `test\u002Fe2e-v2\u002Fcases\u002Flog\u002F` |\n| `profiling\u002Ftrace` | `test\u002Fe2e-v2\u002Fcases\u002Fprofiling\u002Ftrace\u002F` |\n",{"name":42,"description":43,"argument-hint":44,"disable-model-invocation":45},"run-e2e","Run SkyWalking E2E tests locally","[test-case-path]",true,"master","f4d38fdc21e5a929ef89556a1d7c337957523aa9","config\u002Fpi\u002Fagent\u002Fextensions\u002Floop\u002Fskills\u002Fpi-loop",19,"https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles","https:\u002F\u002Fgithub.com\u002Fahmedelgabri","https:\u002F\u002Fgabri.me","…","2026-11-09 00:32:17",{"source":8,"skillName":42,"commands":56,"prompt":66,"zipAvailable":45},[57,60,63],{"tool":58,"command":59},"npx","npx skills add ahmedelgabri\u002Fdotfiles --skill run-e2e",{"tool":61,"command":62},"bunx","bunx skills add ahmedelgabri\u002Fdotfiles --skill run-e2e",{"tool":64,"command":65},"pnpm","pnpm dlx skills add ahmedelgabri\u002Fdotfiles --skill run-e2e","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fpi\u002Fagent\u002Fextensions\u002Floop\u002Fskills\u002Fpi-loop\u002FSKILL.md",[],[69,100,111,128,145,164],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":80,"publishedAt":37},"01M6BTHRPVBTX8Y3EHQYP5464N","youtube-transcript","Retrieve a YouTube video transcript with yt-dlp using human or automatic captions. Save timestamped text locally. Use when asked for a YouTube transcript, subtitles, captions, or spoken text from a YouTube link.","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Fyoutube-transcript\u002FSKILL.md",[75],{"categoryNo":76,"categoryName":77,"taxonomyType":78,"taxonomyTypeDesc":79},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[81,82,83,84,85,24,86,87,88,89,90,91,92,33,93,94,95,96,97,98,99],"youtube","transcript","youtube transcript","when","yt-dlp","video transcript","URL","with","output","link","id","human","text","save","Captions","Video","dlp","file","subtitles",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":37},"01M6BTHPAGHJ9KK6MB8HDC97Q8","unslop","Cut AI tells from any writing NOT code. Must always apply.","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Funslop\u002FSKILL.md",[],[102,107,108,109,110],"cut","code","writing","apply",{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":37},"01M6BTHKTB6SB3JEY6667PXKYP","shannon","Interact with Neovim via RPC to annotate code, navigate files, and do walkthroughs","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Fshannon\u002FSKILL.md",[],[113,118,119,88,120,121,122,123,108,124,125,126,127],"neovim","navigate","rpc","via","Bash","annotate","do","files","read","grep",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":37},"01M6BTHHFSD5H7XR3ZRBSVFVPK","rex","Use the rex CLI to manage Rex sessions, open tabs, arrange panes, run commands, inspect terminal output, and control connected Rex apps.","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Frex\u002FSKILL.md",[],[130,36,89,135,136,137,138,29,33,139,140,141,142,143,144],"apps","control","commands","arrange","panes","open","inspect","sessions","terminal","cli",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":151,"publishedAt":37},"01M6BTHF5G2K3Q990PFD7BEMZ9","pr-review","Review a GitHub pull request, examining the summary, linked resources, and diff","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Fpr-review\u002FSKILL.md",[],[152,153,154,155,126,127,122,156,157,158,159,160,161,87,162,163],"pr","pr review","review","webfetch","pull","pull request","github","diff","request","number","resources","summary",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":170,"publishedAt":37},"01M6BTHC9YP08V0S9HXH78R9FT","pr-create","Create a GitHub pull request as a draft, auto-generating title and body from commits","https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Fpr-create\u002FSKILL.md",[],[152,171,172,158,160,173,174,28,175,176,126,127,122,177,156,157],"pr create","create","body","title","draft","auto","commits"]