-
Notifications
You must be signed in to change notification settings - Fork 110
docs(design): ADR-050 — system-services-monitor scope #1380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dmvevents
wants to merge
6
commits into
NVIDIA:main
Choose a base branch
from
dmvevents:adr-030-system-services-monitor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
db52609
docs(design): ADR-049 system-services-monitor scope and event taxonomy
dmvevents 179212f
docs(design): ADR-049 — address CodeRabbit re-review of scope doc
dmvevents cefcf10
Merge remote-tracking branch 'upstream/main' into adr-030-system-serv…
dmvevents 75f29b9
Merge remote-tracking branch 'upstream/main' into adr-030-system-serv…
dmvevents 35cbf65
docs(design): renumber ADR 049->050 + address re-review round
dmvevents 8a5afd0
docs(design): add Ordering and idempotency section to ADR-050
dmvevents File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,376 @@ | ||
| # ADR-049: System Services Monitor — Scope and Event Taxonomy | ||
|
|
||
| - **Status:** Proposed | ||
| - **Date:** 2026-03-20 (revised 2026-07-24) | ||
| - **Author:** dmvevents | ||
| - **Reviewers:** XRFXLP, lalitadithya | ||
|
|
||
| ## Context | ||
|
|
||
| NVSentinel has no service-level monitoring of the host daemons that keep a GPU | ||
| node's fabric healthy. The existing monitors observe different layers: | ||
|
|
||
| - `gpu-health-monitor` polls DCGM via `pydcgm` for per-GPU device telemetry | ||
| (PCIe, NVLink, thermal). It does not observe host process state. | ||
| - `syslog-health-monitor` parses kernel logs for XID/SXID and fallen-off-bus | ||
| events. It does not observe systemd unit state. | ||
|
|
||
| Neither monitor can tell whether `nvidia-fabricmanager` is running, has hung, or | ||
| is crash-looping, whether `nvidia-persistenced` is up, or whether NVSwitch fabric | ||
| registration is stuck "In Progress" indefinitely. On NVSwitch platforms a dead or | ||
| stuck Fabric Manager silently breaks multi-GPU workloads while every device-level | ||
| and log-level check still reports healthy — the failure is in the *service layer*, | ||
| which nothing currently watches. | ||
|
|
||
| `system-services-monitor` fills that gap: a node-local monitor for non-DCGM, | ||
| non-syslog service health. This ADR defines its scope, the boundary against | ||
| `gpu-health-monitor`, and the health-event taxonomy it emits. | ||
|
|
||
| ## Problem Statement | ||
|
|
||
| Service-level health signals fall into two buckets relative to what NVSentinel | ||
| already collects: | ||
|
|
||
| 1. **Already covered** — per-GPU device health (PCIe, NVLink, clock throttling) | ||
| is owned by `gpu-health-monitor` through DCGM health watches; kernel XID/SXID | ||
| events are owned by `syslog-health-monitor`. | ||
|
|
||
| 2. **Not covered by any monitor** — Fabric Manager process state, FM | ||
| responsiveness, NVSwitch fabric registration state, and GPU-support service | ||
| lifecycle (e.g. `nvidia-persistenced`). These require active host probing | ||
| (systemd queries, `nvidia-smi` fabric-state) that does not fit a passive DCGM | ||
| polling loop or a syslog tail. | ||
|
|
||
| The design goal is to add the second bucket **without** re-collecting the first. | ||
| Re-scraping DCGM-visible signals would create a second collection path for the | ||
| same underlying data, with divergent polling intervals, thresholds, and event | ||
| semantics, and would split the source of truth for GPU device health. | ||
|
|
||
| ## Options Evaluated | ||
|
|
||
| ### Option A: One monitor collects everything (DCGM re-scrape) | ||
|
|
||
| A single new monitor collects FM service health *and* re-derives PCIe/NVLink/clock | ||
| telemetry by scraping the DCGM exporter over HTTP. | ||
|
|
||
| | Advantage | Disadvantage | | ||
| |-----------|-------------| | ||
| | Single component for all fabric-related checks | Re-implements DCGM polling already in `gpu-health-monitor` | | ||
| | No changes to `gpu-health-monitor` | HTTP-scraped metrics are derived; `pydcgm` is authoritative | | ||
| | | Divergent thresholds and event semantics | | ||
| | | Redundant DCGM client code | | ||
|
|
||
| **Assessment:** Not recommended. Creates duplication and a source-of-truth conflict. | ||
|
|
||
| ### Option B: Scope split — device health stays in DCGM path, services get a focused monitor | ||
|
|
||
| Keep DCGM-visible checks in `gpu-health-monitor` via `pydcgm`. Add | ||
| `system-services-monitor` scoped only to non-DCGM service health. | ||
|
|
||
| | Advantage | Disadvantage | | ||
| |-----------|-------------| | ||
| | Aligns device checks with the existing DCGM-based monitor | Needs a clear event taxonomy across monitors | | ||
| | Avoids HTTP-scraping duplication | | | ||
| | Keeps the new monitor focused on what DCGM cannot see | | | ||
| | Reuses the existing health-event envelope and gRPC transport | | | ||
|
|
||
| **Assessment:** Recommended. | ||
|
|
||
| ### Option C: Fold service checks into `gpu-health-monitor` | ||
|
|
||
| Add FM systemd checks and fabric-state probes into `gpu-health-monitor`. | ||
|
|
||
| | Advantage | Disadvantage | | ||
| |-----------|-------------| | ||
| | Single binary for all node health | Active probes (`systemctl`, `nvidia-smi`) risk stalling the DCGM polling loop | | ||
| | | Mixes passive telemetry with active service probing | | ||
| | | `gpu-health-monitor` is not designed for host-level daemon state | | ||
|
|
||
| **Assessment:** Not recommended. Architectural mismatch. | ||
|
|
||
| ## Recommendation | ||
|
|
||
| Adopt **Option B: Scope split**. | ||
|
|
||
| ### Device-health signals stay in `gpu-health-monitor` | ||
|
|
||
| DCGM-visible signals — PCIe link health (`DCGM_HEALTH_WATCH_PCIE`), NVLink fabric | ||
| (`DCGM_HEALTH_WATCH_NVLINK`), and thermal/clock throttling | ||
| (`DCGM_HEALTH_WATCH_THERMAL`) — remain owned by `gpu-health-monitor` via the | ||
| existing `pydcgm` path and are explicitly **out of scope** for | ||
| `system-services-monitor`. "Future degradation-detection work" on these signals | ||
| means additional policy or processing built *on top of* the existing DCGM health | ||
| watches, tracked in `gpu-health-monitor`; it does **not** mean re-implementing or | ||
| re-scraping those watches here. `system-services-monitor` adds no DCGM watches and | ||
| opens no DCGM client. DCGM service health itself (the `nv-hostengine` process) is | ||
| also excluded, because `gpu-health-monitor` already reports it as | ||
| `GpuDcgmConnectivityFailure`. | ||
|
|
||
| ### Service-health signals go to `system-services-monitor` | ||
|
|
||
| `system-services-monitor` is scoped to what DCGM genuinely cannot see: | ||
|
|
||
| - Whether `nvidia-fabricmanager` is running, hung, or crash-looping | ||
| - Whether NVSwitch fabric registration is stuck or errored per GPU | ||
| - Whether GPU-support services (e.g. `nvidia-persistenced`) are up | ||
|
|
||
| These require active systemd and `nvidia-smi` probing that does not belong in a | ||
| passive DCGM polling loop. | ||
|
|
||
| ## Signal Ownership | ||
|
|
||
| | Signal | Source | Owner | | ||
| |--------|--------|-------| | ||
| | PCIe link downtraining | `DCGM_HEALTH_WATCH_PCIE` via `pydcgm` | `gpu-health-monitor` | | ||
| | NVLink degradation | `DCGM_HEALTH_WATCH_NVLINK` via `pydcgm` | `gpu-health-monitor` | | ||
| | GPU clock / thermal throttling | `DCGM_HEALTH_WATCH_THERMAL` via `pydcgm` | `gpu-health-monitor` | | ||
| | DCGM host-engine connectivity | `pydcgm` connect | `gpu-health-monitor` | | ||
| | XID / SXID errors | kernel log parsing | `syslog-health-monitor` | | ||
| | Fabric Manager up/down | `nsenter` → `systemctl show` (`ActiveState`/`SubState`) | `system-services-monitor` | | ||
| | Fabric Manager flapping | `systemctl show NRestarts` + restart-window tracking | `system-services-monitor` | | ||
| | Fabric Manager journal errors | `journalctl -u nvidia-fabricmanager` pattern scan | `system-services-monitor` | | ||
| | NVSwitch fabric registration state | `nvidia-smi --query-gpu=fabric.state,fabric.status` | `system-services-monitor` | | ||
| | GPU service lifecycle (`nvidia-persistenced`) | `nsenter` → `systemctl show` | `system-services-monitor` | | ||
|
|
||
| ## Detection Mechanism | ||
|
|
||
| `system-services-monitor` runs a polling loop (`--poll-interval`, default 30s) that | ||
| executes host probes via `nsenter -t 1 -m --` into PID 1's mount namespace. There | ||
| is **no HTTP health endpoint** — `nvidia-fabricmanager` does not expose one — so | ||
| detection is unit-state and query based: | ||
|
|
||
| - **Fabric Manager service state:** `systemctl show nvidia-fabricmanager | ||
| --property=ActiveState,SubState,MainPID,ExecMainStartTimestamp`. The service is | ||
| considered up when `ActiveState == "active"`. `NRestarts` is queried in a | ||
| separate `systemctl show` call for compatibility with older systemd that omits | ||
| it from combined property lists. | ||
| - **Flap detection:** each new restart (delta in `NRestarts`) is timestamped into a | ||
| per-service deque; the service is "flapping" when the number of restarts inside | ||
| the window (`--flap-window`, default 600s) reaches the threshold | ||
| (`--flap-threshold`, default 3). | ||
| - **Journal errors:** `journalctl -u nvidia-fabricmanager --since "5 minutes ago"` | ||
| is scanned for known patterns and classified as `nvswitch_error`, | ||
| `initialization_failed`, `timeout`, or `general_error`. | ||
| - **Fabric registration state:** `nvidia-smi --query-gpu=index,fabric.state,fabric.status | ||
| --format=csv,noheader` per GPU. A GPU is healthy when `fabric.state == "Completed"` | ||
| and `fabric.status == "Success"`; `fabric.state == "N/A"` (non-NVSwitch topology) | ||
| is skipped. | ||
| - **GPU service lifecycle:** the same `systemctl show` probe for each service in the | ||
| configured list (default `nvidia-persistenced`). | ||
|
|
||
| A boot grace period (`--boot-grace-period`, default 300s) suppresses unhealthy | ||
| alerts during node startup. | ||
|
|
||
| ### FM readiness (Phase 1) | ||
|
|
||
| Fabric Manager is considered ready when it is **systemd `active`** *and* the | ||
| per-GPU fabric-state query shows FM is responsive/progressing — i.e. not in a | ||
| stuck or errored condition (`FM_REGISTRATION_STUCK` / `FM_FABRIC_ERROR`). An | ||
| explicit local API/socket probe is deferred as a future refinement, not a Phase-1 | ||
| prerequisite, because the systemd unit exposes no health endpoint. | ||
|
|
||
| ## Architecture | ||
|
|
||
| ```text | ||
| ┌────────────────────────────────────────────────────────────────────┐ | ||
| │ Node │ | ||
| │ │ | ||
| │ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────┐ │ | ||
| │ │ gpu-health-monitor │ │ syslog-health- │ │ system-services- │ │ | ||
| │ │ │ │ monitor │ │ monitor │ │ | ||
| │ │ DCGM via pydcgm: │ │ │ │ Non-DCGM probes: │ │ | ||
| │ │ - PCIe / NVLink │ │ Kernel log parse: │ │ - FM systemd │ │ | ||
| │ │ - thermal / clock │ │ - XID / SXID │ │ - FM flap / jrnl │ │ | ||
| │ │ - DCGM connectivity│ │ - fallen-off-bus │ │ - fabric state │ │ | ||
| │ │ │ │ │ │ - GPU svc up/down│ │ | ||
| │ │ │ │ │ │ + state cache │ │ | ||
| │ └─────────┬──────────┘ └─────────┬──────────┘ └────────┬─────────┘ │ | ||
| │ │ │ │ │ | ||
| │ └──────────────────────┼──────────────────────┘ │ | ||
| │ ▼ │ | ||
| │ ┌────────────────────────────────────────────────────────────────┐ │ | ||
| │ │ platform-connector │ │ | ||
| │ │ - Aggregates HealthEvents from all node monitors │ │ | ||
| │ │ - gRPC over a node-local Unix domain socket (unix://…) │ │ | ||
| │ │ - Node health state reconciliation │ │ | ||
| │ └────────────────────────────────────────────────────────────────┘ │ | ||
| └────────────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| Transport from a monitor to `platform-connector` is gRPC over a **node-local Unix | ||
| domain socket** (`grpc.insecure_channel("unix://<socket>")`), not a network | ||
| connection, so no TLS is required on this hop. (The gRPC-TLS decision in | ||
| ADR-030 applies to the janitor-controller ↔ janitor-provider path, which is a | ||
| different, potentially cross-namespace connection, and is out of scope here.) | ||
|
|
||
| ## Event Model | ||
|
|
||
| `system-services-monitor` reuses the existing `HealthEvent` envelope — no new | ||
| transport model is required. Every event sets a coarse `checkName` for the | ||
| category and carries the specific condition in `errorCode`. | ||
|
|
||
| | Condition | `checkName` | `errorCode` | `isFatal` | `recommendedAction` | | ||
| |-----------|-------------|-------------|-----------|---------------------| | ||
| | FM not running | `FabricManagerServiceDown` | `FABRIC_MANAGER_NOT_RUNNING` | true | `RESTART_BM` | | ||
| | FM flapping | `FabricManagerServiceDown` | `FABRIC_MANAGER_FLAPPING` | true | `RESTART_BM` | | ||
| | FM journal errors | `FabricManagerServiceDown` | `JOURNAL_NVSWITCH_ERROR` / `JOURNAL_INITIALIZATION_FAILED` / `JOURNAL_TIMEOUT` / `JOURNAL_GENERAL_ERROR` | true | `RESTART_BM` | | ||
| | Fabric registration not started | `FabricStateUnhealthy` | `FM_NOT_STARTED` | true | `RESTART_BM` | | ||
| | Fabric registration stuck | `FabricStateUnhealthy` | `FM_REGISTRATION_STUCK` | true | `RESTART_BM` | | ||
| | Fabric error | `FabricStateUnhealthy` | `FM_FABRIC_ERROR` | true | `RESTART_BM` | | ||
| | GPU support service down | `GpuServiceDown` | `GPU_SERVICE_NOT_RUNNING` | false | `CONTACT_SUPPORT` | | ||
|
|
||
| Multiple `errorCode` values combine on a single event (e.g. an FM-down event that | ||
| is also flapping carries `[FABRIC_MANAGER_NOT_RUNNING, FABRIC_MANAGER_FLAPPING]`). | ||
|
|
||
| ### HealthEvent schema | ||
|
|
||
| Events populate the shared `HealthEvent` protobuf. Fields set by this monitor: | ||
|
|
||
| | Field | Type | Value | | ||
| |-------|------|-------| | ||
| | `version` | int | `1` | | ||
| | `agent` | string | `"system-services-monitor"` | | ||
| | `componentClass` | string | `"INFRASTRUCTURE"` | | ||
| | `checkName` | string | category (table above) | | ||
| | `isFatal` | bool | per condition (table above) | | ||
| | `isHealthy` | bool | `false` on failure, `true` on recovery | | ||
| | `message` | string | human-readable detail | | ||
| | `recommendedAction` | enum `RecommendedAction` | `RESTART_BM` (fatal) / `CONTACT_SUPPORT` (non-fatal) / `NONE` (healthy) | | ||
| | `errorCode` | repeated string | condition codes (table above) | | ||
| | `entitiesImpacted` | repeated `Entity` | `{entityType, entityValue}` — `NODE:<node>` for service checks, `GPU:<index>` for fabric-state checks | | ||
| | `metadata` | map<string,string> | check-specific context | | ||
| | `generatedTimestamp` | Timestamp | event time | | ||
| | `nodeName` | string | node the monitor runs on | | ||
| | `processingStrategy` | enum `ProcessingStrategy` | `EXECUTE_REMEDIATION` (default) or `STORE_ONLY` | | ||
|
|
||
| Example — Fabric Manager down (fatal, node-scoped): | ||
|
|
||
| ```json | ||
| { | ||
| "version": 1, | ||
| "agent": "system-services-monitor", | ||
| "componentClass": "INFRASTRUCTURE", | ||
| "checkName": "FabricManagerServiceDown", | ||
| "isFatal": true, | ||
| "isHealthy": false, | ||
| "message": "Fabric Manager is failed on gpu-node-07", | ||
| "recommendedAction": "RESTART_BM", | ||
| "errorCode": ["FABRIC_MANAGER_NOT_RUNNING"], | ||
| "entitiesImpacted": [{"entityType": "NODE", "entityValue": "gpu-node-07"}], | ||
| "metadata": {"sub_state": "failed", "n_restarts": "4", "flapping": "true"}, | ||
| "nodeName": "gpu-node-07", | ||
| "processingStrategy": "EXECUTE_REMEDIATION" | ||
| } | ||
| ``` | ||
|
|
||
| Example — fabric registration stuck (fatal, GPU-scoped): | ||
|
|
||
| ```json | ||
| { | ||
| "version": 1, | ||
| "agent": "system-services-monitor", | ||
| "componentClass": "INFRASTRUCTURE", | ||
| "checkName": "FabricStateUnhealthy", | ||
| "isFatal": true, | ||
| "isHealthy": false, | ||
| "message": "FM_REGISTRATION_STUCK on gpu-node-07 GPU 7: state=In Progress, status=In Progress", | ||
| "recommendedAction": "RESTART_BM", | ||
| "errorCode": ["FM_REGISTRATION_STUCK"], | ||
| "entitiesImpacted": [{"entityType": "GPU", "entityValue": "7"}], | ||
| "metadata": {"gpu_index": "7", "fabric_state": "In Progress", "fabric_status": "In Progress", "failure_class": "FM_REGISTRATION_STUCK"}, | ||
| "nodeName": "gpu-node-07", | ||
| "processingStrategy": "EXECUTE_REMEDIATION" | ||
| } | ||
| ``` | ||
|
|
||
| Example — GPU support service down (non-fatal, node-scoped): | ||
|
|
||
| ```json | ||
| { | ||
| "version": 1, | ||
| "agent": "system-services-monitor", | ||
| "componentClass": "INFRASTRUCTURE", | ||
| "checkName": "GpuServiceDown", | ||
| "isFatal": false, | ||
| "isHealthy": false, | ||
| "message": "Service nvidia-persistenced is dead on gpu-node-07", | ||
| "recommendedAction": "CONTACT_SUPPORT", | ||
| "errorCode": ["GPU_SERVICE_NOT_RUNNING"], | ||
| "entitiesImpacted": [{"entityType": "NODE", "entityValue": "gpu-node-07"}], | ||
| "metadata": {"service_name": "nvidia-persistenced", "sub_state": "dead"}, | ||
| "nodeName": "gpu-node-07", | ||
| "processingStrategy": "EXECUTE_REMEDIATION" | ||
| } | ||
| ``` | ||
|
|
||
| ## Cached State | ||
|
|
||
| `system-services-monitor` emits **state transitions only**, not per-cycle events. | ||
| The event processor keeps an in-memory `entity_cache` so an unchanged condition is | ||
| not re-sent every poll interval. | ||
|
|
||
| - **Key:** `"<checkName>|<entityType>:<entityValue>"`, built from the impacted | ||
| entities sorted by `(entityType, entityValue)` so key construction is | ||
| order-independent (e.g. `FabricManagerServiceDown|NODE:gpu-node-07`). | ||
| - **Value:** `CachedEntityState(is_fatal, is_healthy)` — the last emitted state for | ||
| that key. An event is sent only when the key is unseen or either `is_fatal` or | ||
| `is_healthy` differs from the cached value. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| - **Concurrency:** the read-decide-write sequence is guarded by a | ||
| `threading.Lock`. Callbacks fire on a `ThreadPoolExecutor`, so without the lock | ||
| two overlapping callbacks could observe the same stale entry and both emit while | ||
| one blocks in the gRPC send. Under the lock, the new state is *reserved* into the | ||
| cache immediately (before the send) so a concurrent callback skips the duplicate. | ||
| - **Rollback:** the gRPC send retries with backoff (up to 5 attempts, ~26 s worst | ||
| case). If it ultimately fails or raises, the reserved cache entries are rolled | ||
| back (popped) so the next poll cycle re-attempts those events. The cache is only | ||
| committed as the authoritative last-sent state after a successful send. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| - **Lifetime:** in-memory for the life of the process; there is no persistence, so | ||
| the cache is empty on restart and the first post-restart cycle re-establishes | ||
| current state. | ||
|
|
||
| ### False-positive mitigations | ||
|
|
||
| - **Boot grace period** (default 300s): suppress unhealthy alerts during node | ||
| startup. | ||
| - **Flap detection**: only flag Fabric Manager as flapping when restarts within the | ||
| configurable window exceed the threshold, rather than on any single restart. | ||
|
|
||
| ## Scope | ||
|
|
||
| In scope for `system-services-monitor`: | ||
|
|
||
| - Fabric Manager systemd health (`ActiveState`/`SubState`) | ||
| - Fabric Manager flap detection and journal-error classification | ||
| - Per-GPU NVSwitch fabric-registration state | ||
| - GPU support-service lifecycle (`nvidia-persistenced`) | ||
| - gRPC client (Unix domain socket), state caching, structured logging, CLI | ||
|
|
||
| Out of scope (owned elsewhere): | ||
|
|
||
| - PCIe, NVLink, thermal/clock telemetry — `gpu-health-monitor` (DCGM watches) | ||
| - DCGM host-engine connectivity — `gpu-health-monitor` (`GpuDcgmConnectivityFailure`) | ||
| - XID / SXID kernel events — `syslog-health-monitor` | ||
|
|
||
| ### Integration testing | ||
|
|
||
| - Device-level faults handled only by `gpu-health-monitor` | ||
| - Kernel XID/SXID handled only by `syslog-health-monitor` | ||
| - Service-level faults handled only by `system-services-monitor` | ||
| - No overlap in event generation for the same underlying condition | ||
| - State-cache deduplication verified (transition-only emission) | ||
| - HealthEvent schema compatibility over gRPC | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
| - Adds service-level monitoring that no existing monitor provides | ||
| - No overlap or source-of-truth conflict with DCGM device health | ||
| - Keeps `system-services-monitor` focused and small | ||
| - Preserves consistent event semantics across monitors | ||
| - Explicit, testable ownership boundary against `gpu-health-monitor` and | ||
| `syslog-health-monitor` | ||
|
|
||
| ### Negative | ||
| - Adds a third node-local monitor DaemonSet to operate | ||
| - Requires the event taxonomy above to stay in sync with remediation policy in | ||
| `platform-connector` | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.