Repository: kubernetes-sigs/aws-ebs-csi-driver

Issue:
Driver fails to remove `ebs.csi.aws.com/agent-not-ready` taint because `eks:node-manager` adds it later
/kind bug

**What happened?**
```
I0418 20:03:39.807826       1 main.go:153] "Initializing metadata"
I0418 20:03:39.812678       1 ec2.go:93] "Number of attached ENIs" attachedENIs=3
I0418 20:03:39.814895       1 metadata.go:48] "Retrieved metadata from IMDS"
I0418 20:03:39.815106       1 mount_linux.go:326] Cannot create temp dir to detect safe 'not mounted' behavior: mkdir /tmp/kubelet-detect-safe-umount3866425026: read-only file system
I0418 20:03:39.815511       1 driver.go:69] "Driver Information" Driver="ebs.csi.aws.com" Version="v1.39.0"
I0418 20:03:39.815681       1 driver.go:138] "Listening for connections" address="/csi/csi.sock"
E0418 20:03:40.826459       1 node.go:855] "Unexpected failure when attempting to remove node taint(s)" err="isAllocatableSet: driver not found on node ip-10-0-124-248.us-east-2.compute.internal"
E0418 20:03:41.334123       1 node.go:855] "Unexpected failure when attempting to remove node taint(s)" err="isAllocatableSet: driver not found on node ip-10-0-124-248.us-east-2.compute.internal"
I0418 20:03:41.365892       1 node.go:574] "NodeGetInfo: called" args=""
I0418 20:03:42.343460       1 node.go:935] "CSINode Allocatable value is set" nodeName="ip-10-0-124-248.us-east-2.compute.internal" count=24
I0418 20:03:42.343483       1 node.go:896] "No taints to remove on node, skipping taint removal"
^C%
```
The driver does not find the `'ebs.csi.aws.com/agent-not-ready=true'` taint, hence its not removed.
`eks:node-manager` taints the node  afterwards, causing the node to be unusable(There is nothing that removes the taint at this point). Restarting the pod resolves the issue because the driver can now find the taint.

There is a race condition that is currently being mitigated (most of the time with) a 1 second delay, set [here](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/pkg/driver/node.go#L75). 


**What you expected to happen?**

The driver attempts to remove the taint after `eks:node-manager` had added it.

**How to reproduce it (as minimally and precisely as possible)?**

**Anything else we need to know?**:
As noted [here](https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/1273#issuecomment-2019290800). It seems like the functionality done for the efs driver needs to be ported. Or at the very least make the delay configurable, instead of 1 second.

**Environment**
- Kubernetes version (use `kubectl version`): Server Version: v1.30.10-eks-bc803b4
- Driver version: 1.39


Hints from issue comments:
@miguelhar: In some cases ~5 seconds later

![Image](https://github.com/user-attachments/assets/2d89c71b-c57c-41bb-b74c-d43342e6d840)@torredil: Hi @miguelhar, thank you for reporting this issue. I briefly discussed it with the team and we agree that bouncing the pod to remove the startup taint after the initial check is performed is not a great experience. Using an informer based solution that watches taints for a brief period seems like a better approach (it resolves the race condition without having to introduce new configuration in the driver).

This is in our backlog now and the enhancement will be included in a future driver release.

Solution patch:
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index b08ed382..04c00a33 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -15,45 +15,45 @@
 version: 2
 enable-beta-ecosystems: true
 updates:
-- package-ecosystem: gomod
-  directories: ["/", "/tests/e2e/"]
-  allow:
-  - dependency-type: "all"
-  schedule:
-    interval: weekly
-    day: "wednesday"
-    time: "06:00"
-    timezone: "America/New_York"
-  groups:
-    k8s-dependencies:
-      patterns:
-        - "k8s.io*"
-        - "sigs.k8s.io*"
-        - "github.com/kubernetes-csi*"
-    misc-dependencies:
-      patterns:
-        - "*"
-      exclude-patterns:
-        - "k8s.io*"
-        - "sigs.k8s.io*"
-        - "github.com/kubernetes-csi*"
-  labels:
-    - "area/dependency"
-    - "ok-to-test"
-    - "release-note-none"
-- package-ecosystem: "github-actions"
-  directory: "/"
-  schedule:
-    interval: weekly
-    # Wednesday chosen to minimize time after Windows Patch Tuesdays
-    day: "wednesday"
-    time: "06:00"
-    timezone: "America/New_York"
-  groups:
-    actions:
-      patterns:
-        - "*"
-  labels:
-    - "area/dependency"
-    - "ok-to-test"
-    - "release-note-none"
+  - package-ecosystem: gomod
+    directories: ["/", "/tests/e2e/"]
+    allow:
+      - dependency-type: "all"
+    schedule:
+      interval: weekly
+      day: "wednesday"
+      time: "06:00"
+      timezone: "America/New_York"
+    groups:
+      k8s-dependencies:
+        patterns:
+          - "k8s.io*"
+          - "sigs.k8s.io*"
+          - "github.com/kubernetes-csi*"
+      misc-dependencies:
+        patterns:
+          - "*"
+        exclude-patterns:
+          - "k8s.io*"
+          - "sigs.k8s.io*"
+          - "github.com/kubernetes-csi*"
+    labels:
+      - "area/dependency"
+      - "ok-to-test"
+      - "release-note-none"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: weekly
+      # Wednesday chosen to minimize time after Windows Patch Tuesdays
+      day: "wednesday"
+      time: "06:00"
+      timezone: "America/New_York"
+    groups:
+      actions:
+        patterns:
+          - "*"
+    labels:
+      - "area/dependency"
+      - "ok-to-test"
+      - "release-note-none"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67c0f4cc..40e9598c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,191 @@
+# 1.44.0
+
+## Changes by Kind
+
+### Feature
+
+- Remove support for Snow Devices. See https://github.com/kubernetes-sigs/aws-ebs-csi-driver/issues/2365 ([#2467](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/pull/2467), [@AndrewSirenko](https://github.com/AndrewSirenko))
+
+### Bug or Regression
+
+- Increase io2 volume maximum iops:size ratio from 500:1 to 1000:1 to match EC2 behavior ([#2475](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/pull/2475), [@AndrewSirenko](https://github.com/AndrewSirenko))
+
+## Dependencies
+
+### Added
+- github.com/golang/groupcache: [2c02b82](https://github.com/golang/groupcache/tree/2c02b82)
+- github.com/imdario/mergo: [v0.3.16](https://github.com/imdario/mergo/tree/v0.3.16)
+
+### Changed
+- cel.dev/expr: v0.23.1 → v0.20.0
+- cloud.google.com/go: v0.34.0 → v0.26.0
+- github.com/aws/aws-sdk-go-v2/service/ec2: [v1.215.0 → v1.218.0](https://github.com/aws/aws-sdk-go-v2/compare/service/ec2/v1.215.0...service/ec2/v1.218.0)
+- github.com/awslabs/volume-modifier-for-k8s: [v0.5.1 → v0.5.0](https://github.com/awslabs/volume-modifier-for-k8s/compare/v0.5.1...v0.5.0)
+- github.com/google/pprof: [27863c8 → 40e02aa](https://github.com/google/pprof/compare/27863c8...40e02aa)
+- github.com/kubernetes-csi/csi-lib-utils: [v0.20.0 → v0.19.0](https://github.com/kubernetes-csi/csi-lib-utils/compare/v0.20.0...v0.19.0)
+- github.com/kubernetes-csi/external-resizer: [v1.13.1 → v1.12.0](https://github.com/kubernetes-csi/external-resizer/compare/v1.13.1...v1.12.0)
+- github.com/onsi/ginkgo/v2: [v2.23.4 → v2.21.0](https://github.com/onsi/ginkgo/compare/v2.23.4...v2.21.0)
+- github.com/onsi/gomega: [v1.37.0 → v1.35.1](https://github.com/onsi/gomega/compare/v1.37.0...v1.35.1)
+- github.com/prometheus/common: [v0.63.0 → v0.64.0](https://github.com/prometheus/common/compare/v0.63.0...v0.64.0)
+- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp: v0.60.0 → v0.58.0
+- go.opentelemetry.io/otel/sdk/metric: v1.35.0 → v1.34.0
+- go.opentelemetry.io/proto/otlp: v1.5.0 → v1.6.0
+- golang.org/x/crypto: v0.37.0 → v0.38.0
+- golang.org/x/net: v0.39.0 → v0.40.0
+- golang.org/x/oauth2: v0.29.0 → v0.30.0
+- golang.org/x/sync: v0.13.0 → v0.14.0
+- golang.org/x/term: v0.31.0 → v0.32.0
+- golang.org/x/text: v0.24.0 → v0.25.0
+- golang.org/x/tools: v0.32.0 → v0.33.0
+- google.golang.org/genproto/googleapis/api: de1ac95 → 55703ea
+- google.golang.org/genproto/googleapis/rpc: de1ac95 → 55703ea
+- google.golang.org/genproto: ef43131 → cb27e3a
+- google.golang.org/grpc: v1.72.0 → v1.72.1
+- k8s.io/api: v0.33.0 → v0.33.1
+- k8s.io/apimachinery: v0.33.0 → v0.33.1
+- k8s.io/client-go: v0.33.0 → v0.33.1
+- k8s.io/component-base: v0.33.0 → v0.33.1
+- k8s.io/csi-translation-lib: v0.33.0 → v0.31.4
+- k8s.io/gengo/v2: 1244d31 → a7b603a
+- k8s.io/kubectl: v0.33.0 → v0.31.4
+- k8s.io/mount-utils: v0.33.0 → v0.33.1
+
+### Removed
+- bitbucket.org/bertimus9/systemstat: v0.5.0
+- github.com/JeffAshton/win_pdh: [76bb4ee](https://github.com/JeffAshton/win_pdh/tree/76bb4ee)
+- github.com/MakeNowJust/heredoc: [v1.0.0](https://github.com/MakeNowJust/heredoc/tree/v1.0.0)
+- github.com/Microsoft/hnslib: [v0.0.8](https://github.com/Microsoft/hnslib/tree/v0.0.8)
+- github.com/antlr4-go/antlr/v4: [v4.13.1](https://github.com/antlr4-go/antlr/tree/v4.13.1)
+- github.com/armon/circbuf: [5111143](https://github.com/armon/circbuf/tree/5111143)
+- github.com/chai2010/gettext-go: [v1.0.2](https://github.com/chai2010/gettext-go/tree/v1.0.2)
+- github.com/chromedp/cdproto: [3cf4e6d](https://github.com/chromedp/cdproto/tree/3cf4e6d)
+- github.com/chromedp/chromedp: [v0.9.2](https://github.com/chromedp/chromedp/tree/v0.9.2)
+- github.com/chromedp/sysutil: [v1.0.0](https://github.com/chromedp/sysutil/tree/v1.0.0)
+- github.com/chzyer/logex: [v1.2.1](https://github.com/chzyer/logex/tree/v1.2.1)
+- github.com/chzyer/test: [v1.0.0](https://github.com/chzyer/test/tree/v1.0.0)
+- github.com/cilium/ebpf: [v0.17.3](https://github.com/cilium/ebpf/tree/v0.17.3)
+- github.com/containerd/containerd/api: [v1.8.0](https://github.com/containerd/containerd/tree/api/v1.8.0)
+- github.com/containerd/errdefs/pkg: [v0.3.0](https://github.com/containerd/errdefs/tree/pkg/v0.3.0)
+- github.com/containerd/errdefs: [v1.0.0](https://github.com/containerd/errdefs/tree/v1.0.0)
+- github.com/containerd/log: [v0.1.0](https://github.com/containerd/log/tree/v0.1.0)
+- github.com/containerd/ttrpc: [v1.2.7](https://github.com/containerd/ttrpc/tree/v1.2.7)
+- github.com/containerd/typeurl/v2: [v2.2.3](https://github.com/containerd/typeurl/tree/v2.2.3)
+- github.com/coredns/caddy: [v1.1.1](https://github.com/coredns/caddy/tree/v1.1.1)
+- github.com/coredns/corefile-migration: [v1.0.25](https://github.com/coredns/corefile-migration/tree/v1.0.25)
+- github.com/coreos/go-oidc: [v2.3.0+incompatible](https://github.com/coreos/go-oidc/tree/v2.3.0)
+- github.com/coreos/go-semver: [v0.3.1](https://github.com/coreos/go-semver/tree/v0.3.1)
+- github.com/coreos/go-systemd/v22: [v22.5.0](https://github.com/coreos/go-systemd/tree/v22.5.0)
+- github.com/creack/pty: [v1.1.9](https://github.com/creack/pty/tree/v1.1.9)
+- github.com/cyphar/filepath-securejoin: [v0.4.1](https://github.com/cyphar/filepath-securejoin/tree/v0.4.1)
+- github.com/distribution/reference: [v0.6.0](https://github.com/distribution/reference/tree/v0.6.0)
+- github.com/docker/docker: [v26.1.4+incompatible](https://github.com/docker/docker/tree/v26.1.4)
+- github.com/docker/go-connections: [v0.5.0](https://github.com/docker/go-connections/tree/v0.5.0)
+- github.com/docker/go-units: [v0.5.0](https://github.com/docker/go-units/tree/v0.5.0)
+- github.com/dustin/go-humanize: [v1.0.1](https://github.com/dustin/go-humanize/tree/v1.0.1)
+- github.com/euank/go-kmsg-parser: [v2.0.0+incompatible](https://github.com/euank/go-kmsg-parser/tree/v2.0.0)
+- github.com/exponent-io/jsonpath: [1de76d7](https://github.com/exponent-io/jsonpath/tree/1de76d7)
+- github.com/fatih/camelcase: [v1.0.0](https://github.com/fatih/camelcase/tree/v1.0.0)
+- github.com/fsnotify/fsnotify: [v1.9.0](https://github.com/fsnotify/fsnotify/tree/v1.9.0)
+- github.com/go-errors/errors: [v1.4.2](https://github.com/go-errors/errors/tree/v1.4.2)
+- github.com/gobwas/httphead: [v0.1.0](https://github.com/gobwas/httphead/tree/v0.1.0)
+- github.com/gobwas/pool: [v0.2.1](https://github.com/gobwas/pool/tree/v0.2.1)
+- github.com/gobwas/ws: [v1.2.1](https://github.com/gobwas/ws/tree/v1.2.1)
+- github.com/godbus/dbus/v5: [v5.1.0](https://github.com/godbus/dbus/tree/v5.1.0)
+- github.com/golang-jwt/jwt/v4: [v4.5.2](https://github.com/golang-jwt/jwt/tree/v4.5.2)
+- github.com/google/cadvisor: [v0.52.1](https://github.com/google/cadvisor/tree/v0.52.1)
+- github.com/google/cel-go: [v0.23.2](https://github.com/google/cel-go/tree/v0.23.2)
+- github.com/google/shlex: [e7afc7f](https://github.com/google/shlex/tree/e7afc7f)
+- github.com/grpc-ecosystem/go-grpc-middleware: [v1.3.0](https://github.com/grpc-ecosystem/go-grpc-middleware/tree/v1.3.0)
+- github.com/grpc-ecosystem/go-grpc-prometheus: [v1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/tree/v1.2.0)
+- github.com/grpc-ecosystem/grpc-gateway: [v1.16.0](https://github.com/grpc-ecosystem/grpc-gateway/tree/v1.16.0)
+- github.com/hpcloud/tail: [v1.0.0](https://github.com/hpcloud/tail/tree/v1.0.0)
+- github.com/ishidawataru/sctp: [7ff4192](https://github.com/ishidawataru/sctp/tree/7ff4192)
+- github.com/jonboulle/clockwork: [v0.4.0](https://github.com/jonboulle/clockwork/tree/v0.4.0)
+- github.com/karrick/godirwalk: [v1.17.0](https://github.com/karrick/godirwalk/tree/v1.17.0)
+- github.com/kr/pty: [v1.1.1](https://github.com/kr/pty/tree/v1.1.1)
+- github.com/kubernetes-csi/external-snapshotter/client/v4: [v4.2.0](https://github.com/kubernetes-csi/external-snapshotter/tree/client/v4/v4.2.0)
+- github.com/ledongthuc/pdf: [0c2507a](https://github.com/ledongthuc/pdf/tree/0c2507a)
+- github.com/libopenstorage/openstorage: [v1.0.0](https://github.com/libopenstorage/openstorage/tree/v1.0.0)
+- github.com/liggitt/tabwriter: [89fcab3](https://github.com/liggitt/tabwriter/tree/89fcab3)
+- github.com/lithammer/dedent: [v1.1.0](https://github.com/lithammer/dedent/tree/v1.1.0)
+- github.com/matttproud/golang_protobuf_extensions: [v1.0.1](https://github.com/matttproud/golang_protobuf_extensions/tree/v1.0.1)
+- github.com/mistifyio/go-zfs: [f784269](https://github.com/mistifyio/go-zfs/tree/f784269)
+- github.com/mitchellh/go-wordwrap: [v1.0.1](https://github.com/mitchellh/go-wordwrap/tree/v1.0.1)
+- github.com/moby/docker-image-spec: [v1.3.1](https://github.com/moby/docker-image-spec/tree/v1.3.1)
+- github.com/moby/ipvs: [v1.1.0](https://github.com/moby/ipvs/tree/v1.1.0)
+- github.com/moby/sys/userns: [v0.1.0](https://github.com/moby/sys/tree/userns/v0.1.0)
+- github.com/mohae/deepcopy: [c48cc78](https://github.com/mohae/deepcopy/tree/c48cc78)
+- github.com/monochromegane/go-gitignore: [205db1a](https://github.com/monochromegane/go-gitignore/tree/205db1a)
+- github.com/morikuni/aec: [v1.0.0](https://github.com/morikuni/aec/tree/v1.0.0)
+- github.com/mrunalp/fileutils: [v0.5.1](https://github.com/mrunalp/fileutils/tree/v0.5.1)
+- github.com/nxadm/tail: [v1.4.8](https://github.com/nxadm/tail/tree/v1.4.8)
+- github.com/onsi/ginkgo: [v1.16.4](https://github.com/onsi/ginkgo/tree/v1.16.4)
+- github.com/opencontainers/cgroups: [v0.0.1](https://github.com/opencontainers/cgroups/tree/v0.0.1)
+- github.com/opencontainers/go-digest: [v1.0.0](https://github.com/opencontainers/go-digest/tree/v1.0.0)
+- github.com/opencontainers/image-spec: [v1.1.1](https://github.com/opencontainers/image-spec/tree/v1.1.1)
+- github.com/opencontainers/runc: [v1.2.5](https://github.com/opencontainers/runc/tree/v1.2.5)
+- github.com/opencontainers/runtime-spec: [v1.2.1](https://github.com/opencontainers/runtime-spec/tree/v1.2.1)
+- github.com/opencontainers/selinux: [v1.12.0](https://github.com/opencontainers/selinux/tree/v1.12.0)
+- github.com/orisano/pixelmatch: [fb0b554](https://github.com/orisano/pixelmatch/tree/fb0b554)
+- github.com/pkg/diff: [20ebb0f](https://github.com/pkg/diff/tree/20ebb0f)
+- github.com/pquerna/cachecontrol: [v0.1.0](https://github.com/pquerna/cachecontrol/tree/v0.1.0)
+- github.com/prashantv/gostub: [v1.1.0](https://github.com/prashantv/gostub/tree/v1.1.0)
+- github.com/robfig/cron/v3: [v3.0.1](https://github.com/robfig/cron/tree/v3.0.1)
+- github.com/russross/blackfriday: [v1.6.0](https://github.com/russross/blackfriday/tree/v1.6.0)
+- github.com/santhosh-tekuri/jsonschema/v5: [v5.3.1](https://github.com/santhosh-tekuri/jsonschema/tree/v5.3.1)
+- github.com/soheilhy/cmux: [v0.1.5](https://github.com/soheilhy/cmux/tree/v0.1.5)
+- github.com/stoewer/go-strcase: [v1.3.0](https://github.com/stoewer/go-strcase/tree/v1.3.0)
+- github.com/tmc/grpc-websocket-proxy: [673ab2c](https://github.com/tmc/grpc-websocket-proxy/tree/673ab2c)
+- github.com/vishvananda/netlink: [62fb240](https://github.com/vishvananda/netlink/tree/62fb240)
+- github.com/vishvananda/netns: [v0.0.4](https://github.com/vishvananda/netns/tree/v0.0.4)
+- github.com/xiang90/probing: [a49e3df](https://github.com/xiang90/probing/tree/a49e3df)
+- github.com/xlab/treeprint: [v1.2.0](https://github.com/xlab/treeprint/tree/v1.2.0)
+- go.etcd.io/bbolt: v1.3.11
+- go.etcd.io/etcd/api/v3: v3.5.21
+- go.etcd.io/etcd/client/pkg/v3: v3.5.21
+- go.etcd.io/etcd/client/v2: v2.305.21
+- go.etcd.io/etcd/client/v3: v3.5.21
+- go.etcd.io/etcd/pkg/v3: v3.5.21
+- go.etcd.io/etcd/raft/v3: v3.5.21
+- go.etcd.io/etcd/server/v3: v3.5.21
+- go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful: v0.42.0
+- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp: v1.27.0
+- go.uber.org/automaxprocs: v1.6.0
+- gopkg.in/fsnotify.v1: v1.4.7
+- gopkg.in/go-jose/go-jose.v2: v2.6.3
+- gopkg.in/natefinch/lumberjack.v2: v2.2.1
+- gopkg.in/tomb.v1: dd63297
+- gotest.tools/v3: v3.0.2
+- k8s.io/apiextensions-apiserver: v0.33.0
+- k8s.io/apiserver: v0.33.0
+- k8s.io/cli-runtime: v0.33.0
+- k8s.io/cloud-provider: v0.33.0
+- k8s.io/cluster-bootstrap: v0.33.0
+- k8s.io/code-generator: v0.33.0
+- k8s.io/component-helpers: v0.33.0
+- k8s.io/controller-manager: v0.33.0
+- k8s.io/cri-api: v0.33.0
+- k8s.io/cri-client: v0.33.0
+- k8s.io/dynamic-resource-allocation: v0.33.0
+- k8s.io/endpointslice: v0.33.0
+- k8s.io/externaljwt: v0.33.0
+- k8s.io/kms: v0.33.0
+- k8s.io/kube-aggregator: v0.33.0
+- k8s.io/kube-controller-manager: v0.33.0
+- k8s.io/kube-proxy: v0.33.0
+- k8s.io/kube-scheduler: v0.33.0
+- k8s.io/kubelet: v0.33.0
+- k8s.io/kubernetes: v1.33.0
+- k8s.io/metrics: v0.33.0
+- k8s.io/pod-security-admission: v0.33.0
+- k8s.io/sample-apiserver: v0.33.0
+- k8s.io/system-validators: v1.9.1
+- sigs.k8s.io/apiserver-network-proxy/konnectivity-client: v0.32.0
+- sigs.k8s.io/knftables: v0.0.17
+- sigs.k8s.io/kustomize/api: v0.19.0
+- sigs.k8s.io/kustomize/kustomize/v5: v5.6.0
+- sigs.k8s.io/kustomize/kyaml: v0.19.0
+
 # 1.43.0
 
 ## Changes by Kind
diff --git a/Makefile b/Makefile
index be17f60b..94aa0c8d 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@
 
 ## Variables/Functions
 
-VERSION?=v1.43.0
+VERSION?=v1.44.0
 
 PKG=github.com/kubernetes-sigs/aws-ebs-csi-driver
 GIT_COMMIT?=$(shell git rev-parse HEAD)
diff --git a/README.md b/README.md
index eaf4525e..8f279fe2 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,6 @@
 [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/kubernetes-sigs/aws-ebs-csi-driver)](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/releases)
 [![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes-sigs/aws-ebs-csi-driver)](https://goreportcard.com/report/github.com/kubernetes-sigs/aws-ebs-csi-driver)
 
-> [!CAUTION]
-> ## [Deprecation announcement] AWS Snow Family device support for the EBS CSI Driver
-> Support for the EBS CSI Driver on [AWS Snow Family devices](https://aws.amazon.com/snowball/) is deprecated, effective immediately. No further Snow-specific bugfixes or feature requests will be merged. The existing functionality for Snow devices will be removed in the 1.44 release of the EBS CSI Driver. This announcement does not affect the support of the EBS CSI Driver on other platforms, such as [Amazon EC2](https://aws.amazon.com/ec2/) or EC2 on [AWS Outposts](https://aws.amazon.com/outposts/). For any questions related to this announcement, please comment on this issue [#2365](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/issues/2365) or open a new issue.
-
-
 ## Overview
 
 The [Amazon Elastic Block Store](https://aws.amazon.com/ebs/) Container Storage Interface (CSI) Driver provides a [CSI](https://github.com/container-storage-interface/spec/blob/master/spec.md) interface used by Container Orchestrators to manage the lifecycle of Amazon EBS volumes.
@@ -24,8 +19,8 @@ The [Amazon Elastic Block Store](https://aws.amazon.com/ebs/) Container Storage
 
 | Driver Version | [registry.k8s.io](https://kubernetes.io/blog/2022/11/28/registry-k8s-io-faster-cheaper-ga/) Image | [ECR Public](https://gallery.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver) Image |
 |----------------|---------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
+| v1.44.0        | registry.k8s.io/provider-aws/aws-ebs-csi-driver:v1.44.0                                           | public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.44.0                      |
 | v1.43.0        | registry.k8s.io/provider-aws/aws-ebs-csi-driver:v1.43.0                                           | public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.43.0                      |
-| v1.42.0        | registry.k8s.io/provider-aws/aws-ebs-csi-driver:v1.42.0                                           | public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.42.0                      |
 
 ## Releases
 
diff --git a/charts/aws-ebs-csi-driver/CHANGELOG.md b/charts/aws-ebs-csi-driver/CHANGELOG.md
index b676490d..1ff8389d 100644
--- a/charts/aws-ebs-csi-driver/CHANGELOG.md
+++ b/charts/aws-ebs-csi-driver/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Helm chart
 
+## v2.44.0
+
+### Feature
+
+- Bump driver version to `v1.44.0`.
+
 ## v2.43.0
 
 ### Feature
diff --git a/charts/aws-ebs-csi-driver/Chart.yaml b/charts/aws-ebs-csi-driver/Chart.yaml
index d7094580..cf1305f2 100644
--- a/charts/aws-ebs-csi-driver/Chart.yaml
+++ b/charts/aws-ebs-csi-driver/Chart.yaml
@@ -1,8 +1,8 @@
 apiVersion: v2
-appVersion: 1.43.0
+appVersion: 1.44.0
 name: aws-ebs-csi-driver
 description: A Helm chart for AWS EBS CSI Driver
-version: 2.43.0
+version: 2.44.0
 kubeVersion: ">=1.17.0-0"
 home: https://github.com/kubernetes-sigs/aws-ebs-csi-driver
 sources:
diff --git a/charts/aws-ebs-csi-driver/templates/NOTES.txt b/charts/aws-ebs-csi-driver/templates/NOTES.txt
index a6e2aa34..414aed67 100644
--- a/charts/aws-ebs-csi-driver/templates/NOTES.txt
+++ b/charts/aws-ebs-csi-driver/templates/NOTES.txt
@@ -4,4 +4,4 @@ To verify that aws-ebs-csi-driver has started, run:
 
 [Deprecation announcement] AWS Snow Family device support for the EBS CSI Driver
 
-Support for the EBS CSI Driver on [AWS Snow Family devices](https://aws.amazon.com/snowball/) is deprecated, effective immediately. No further Snow-specific bugfixes or feature requests will be merged. The existing functionality for Snow devices will be removed the 1.44 release of the EBS CSI Driver. This announcement does not affect the support of the EBS CSI Driver on other platforms, such as [Amazon EC2](https://aws.amazon.com/ec2/) or EC2 on [AWS Outposts](https://aws.amazon.com/outposts/). For any questions related to this announcement, please comment on this issue [#2365](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/issues/2365) or open a new issue.
\ No newline at end of file
+Support for the EBS CSI Driver on [AWS Snow Family devices](https://aws.amazon.com/snowball/) is deprecated, effective immediately. No further Snow-specific bugfixes or feature requests will be merged. The existing functionality for Snow devices has been removed on the 1.44 release of the EBS CSI Driver. Support of the EBS CSI Driver on other platforms, such as [Amazon EC2](https://aws.amazon.com/ec2/) or EC2 on [AWS Outposts](https://aws.amazon.com/outposts/) is not affected.
\ No newline at end of file
diff --git a/charts/aws-ebs-csi-driver/templates/clusterrole-csi-node.yaml b/charts/aws-ebs-csi-driver/templates/clusterrole-csi-node.yaml
index 2b7295aa..c04310f6 100644
--- a/charts/aws-ebs-csi-driver/templates/clusterrole-csi-node.yaml
+++ b/charts/aws-ebs-csi-driver/templates/clusterrole-csi-node.yaml
@@ -8,7 +8,7 @@ metadata:
 rules:
   - apiGroups: [""]
     resources: ["nodes"]
-    verbs: ["get", "patch"]
+    verbs: ["get", "patch", "list", "watch"]
   - apiGroups: ["storage.k8s.io"]
     resources: ["volumeattachments"]
     verbs: ["get", "list", "watch"]
diff --git a/deploy/kubernetes/base/clusterrole-csi-node.yaml b/deploy/kubernetes/base/clusterrole-csi-node.yaml
index f4d08b63..77bac09e 100644
--- a/deploy/kubernetes/base/clusterrole-csi-node.yaml
+++ b/deploy/kubernetes/base/clusterrole-csi-node.yaml
@@ -9,7 +9,7 @@ metadata:
 rules:
   - apiGroups: [""]
     resources: ["nodes"]
-    verbs: ["get", "patch"]
+    verbs: ["get", "patch", "list", "watch"]
   - apiGroups: ["storage.k8s.io"]
     resources: ["volumeattachments"]
     verbs: ["get", "list", "watch"]
diff --git a/deploy/kubernetes/base/controller.yaml b/deploy/kubernetes/base/controller.yaml
index e3b03e91..e29a10de 100644
--- a/deploy/kubernetes/base/controller.yaml
+++ b/deploy/kubernetes/base/controller.yaml
@@ -64,7 +64,7 @@ spec:
         runAsUser: 1000
       containers:
         - name: ebs-plugin
-          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.43.0
+          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.44.0
           imagePullPolicy: IfNotPresent
           args:
             - controller
diff --git a/deploy/kubernetes/base/node-windows.yaml b/deploy/kubernetes/base/node-windows.yaml
index ab9b7baa..6e778a4f 100644
--- a/deploy/kubernetes/base/node-windows.yaml
+++ b/deploy/kubernetes/base/node-windows.yaml
@@ -50,7 +50,7 @@ spec:
         - operator: Exists
       containers:
         - name: ebs-plugin
-          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.43.0
+          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.44.0
           imagePullPolicy: IfNotPresent
           args:
             - node
diff --git a/deploy/kubernetes/base/node.yaml b/deploy/kubernetes/base/node.yaml
index 186cf28e..67af351e 100644
--- a/deploy/kubernetes/base/node.yaml
+++ b/deploy/kubernetes/base/node.yaml
@@ -56,7 +56,7 @@ spec:
         runAsUser: 0
       containers:
         - name: ebs-plugin
-          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.43.0
+          image: public.ecr.aws/ebs-csi-driver/aws-ebs-csi-driver:v1.44.0
           imagePullPolicy: IfNotPresent
           args:
             - node
diff --git a/docs/install.md b/docs/install.md
index d5c8cdbf..8723c976 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -127,7 +127,7 @@ You may deploy the EBS CSI driver via Kustomize, Helm, or as an [Amazon EKS mana
 
 #### Kustomize
 ```sh
-kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.43"
+kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.44"
 ```
 
 *Note: Using the master branch to deploy the driver is not supported as the master branch may contain upcoming features incompatible with the currently released stable version of the driver.*
diff --git a/pkg/driver/node.go b/pkg/driver/node.go
index f1a4a4f7..c1fb6530 100644
--- a/pkg/driver/node.go
+++ b/pkg/driver/node.go
@@ -38,7 +38,9 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	k8stypes "k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/informers"
 	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/cache"
 	"k8s.io/klog/v2"
 )
 
@@ -66,15 +68,11 @@ var (
 		csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
 		csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
 	}
+)
 
-	// taintRemovalInitialDelay is the initial delay for node taint removal.
-	taintRemovalInitialDelay = 1 * time.Second
-	// taintRemovalBackoff is the exponential backoff configuration for node taint removal.
-	taintRemovalBackoff = wait.Backoff{
-		Duration: 500 * time.Millisecond,
-		Factor:   2,
-		Steps:    10, // Max delay = 0.5 * 2^9 = ~4 minutes
-	}
+const (
+	// taintWatcherDuration is the maximum duration for the not-ready taint watcher to run.
+	taintWatcherDuration = 1 * time.Minute
 )
 
 // NodeService represents the node service of CSI driver.
@@ -89,11 +87,9 @@ type NodeService struct {
 // NewNodeService creates a new node service.
 func NewNodeService(o *Options, md metadata.MetadataService, m mounter.Mounter, k kubernetes.Interface) *NodeService {
 	if k != nil {
-		// Remove taint from node to indicate driver startup success
-		// This is done at the last possible moment to prevent race conditions or false positive removals
-		time.AfterFunc(taintRemovalInitialDelay, func() {
-			removeTaintInBackground(k, taintRemovalBackoff, removeNotReadyTaint)
-		})
+		// Watch for the agent‑not‑ready taint for up to one minute and remove it
+		// as soon as allocatable is available.
+		startNotReadyTaintWatcher(k, taintWatcherDuration)
 	}
 
 	return &NodeService{
@@ -848,45 +844,111 @@ func collectMountOptions(fsType string, mntFlags []string) []string {
 	return options
 }
 
-// Struct for JSON patch operations.
-type JSONPatch struct {
-	OP    string      `json:"op,omitempty"`
-	Path  string      `json:"path,omitempty"`
-	Value interface{} `json:"value"`
-}
+// startNotReadyTaintWatcher launches a short‑lived Node informer that removes the
+// ebs.csi.aws.com/agent‑not‑ready taint. The informer is stopped after maxWatchDuration.
+func startNotReadyTaintWatcher(clientset kubernetes.Interface, maxWatchDuration time.Duration) {
+	nodeName := os.Getenv("CSI_NODE_NAME")
+	if nodeName == "" {
+		klog.V(4).InfoS("CSI_NODE_NAME missing, skipping taint watcher")
+		return
+	}
+
+	factory := informers.NewSharedInformerFactoryWithOptions(
+		clientset,
+		0, // No resync
+		informers.WithTweakListOptions(func(lo *metav1.ListOptions) {
+			lo.FieldSelector = "metadata.name=" + nodeName
+		}),
+	)
+	informer := factory.Core().V1().Nodes().Informer()
+
+	attemptTaintRemoval := func(n *corev1.Node) {
+		if !hasNotReadyTaint(n) {
+			return
+		}
+
+		backoff := wait.Backoff{
+			Duration: 2 * time.Second,
+			Factor:   1.5,
+			Steps:    5,
+		}
+
+		ctx, cancel := context.WithTimeout(context.Background(), maxWatchDuration)
+		defer cancel()
+
+		err := wait.ExponentialBackoffWithContext(ctx, backoff, func(ctx context.Context) (bool, error) {
+			if err := removeNotReadyTaint(ctx, clientset, n); err != nil {
+				klog.ErrorS(err, "Failed to remove agent-not-ready taint, retrying", "node", n.Name)
+				return false, nil // Continue retrying
+			}
+			klog.V(2).InfoS("Successfully removed agent-not-ready taint", "node", n.Name)
+			return true, nil
+		})
 
-// removeTaintInBackground is a goroutine that retries removeNotReadyTaint with exponential backoff.
-func removeTaintInBackground(k8sClient kubernetes.Interface, backoff wait.Backoff, removalFunc func(kubernetes.Interface) error) {
-	backoffErr := wait.ExponentialBackoff(backoff, func() (bool, error) {
-		err := removalFunc(k8sClient)
 		if err != nil {
-			klog.ErrorS(err, "Unexpected failure when attempting to remove node taint(s)")
-			return false, nil
+			klog.ErrorS(err, "Timed out trying to remove agent-not-ready taint", "node", n.Name)
+		}
+	}
+
+	if _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc: func(obj interface{}) {
+			if n, ok := obj.(*corev1.Node); ok {
+				attemptTaintRemoval(n)
+			}
+		},
+		UpdateFunc: func(_, newObj interface{}) {
+			if n, ok := newObj.(*corev1.Node); ok {
+				attemptTaintRemoval(n)
+			}
+		},
+	}); err != nil {
+		klog.ErrorS(err, "Taint‑watcher: failed to add event handler")
+		return
+	}
+
+	stopCh := make(chan struct{})
+
+	go func() {
+		factory.Start(stopCh)
+
+		if ok := cache.WaitForCacheSync(stopCh, informer.HasSynced); !ok {
+			klog.ErrorS(nil, "Taint-watcher: cache sync failed")
 		}
-		return true, nil
-	})
 
-	if backoffErr != nil {
-		klog.ErrorS(backoffErr, "Retries exhausted, giving up attempting to remove node taint(s)")
+		// Immediate scan in case the taint is already present and no event fires.
+		if obj, exists, err := informer.GetStore().GetByKey(nodeName); err == nil && exists {
+			if n, ok := obj.(*corev1.Node); ok {
+				attemptTaintRemoval(n)
+			}
+		}
+
+		<-time.After(maxWatchDuration)
+		klog.V(8).InfoS("Taint-watcher timeout reached; stopping")
+		close(stopCh)
+	}()
+}
+
+func hasNotReadyTaint(n *corev1.Node) bool {
+	for _, t := range n.Spec.Taints {
+		if t.Key == AgentNotReadyNodeTaintKey {
+			return true
+		}
 	}
+	return false
+}
+
+// Struct for JSON patch operations.
+type JSONPatch struct {
+	OP    string      `json:"op,omitempty"`
+	Path  string      `json:"path,omitempty"`
+	Value interface{} `json:"value"`
 }
 
 // removeNotReadyTaint removes the taint ebs.csi.aws.com/agent-not-ready from the local node
 // This taint can be optionally applied by users to prevent startup race conditions such as
 // https://github.com/kubernetes/kubernetes/issues/95911
-func removeNotReadyTaint(clientset kubernetes.Interface) error {
-	nodeName := os.Getenv("CSI_NODE_NAME")
-	if nodeName == "" {
-		klog.V(4).InfoS("CSI_NODE_NAME missing, skipping taint removal")
-		return nil
-	}
-
-	node, err := clientset.CoreV1().Nodes().Get(context.Background(), nodeName, metav1.GetOptions{})
-	if err != nil {
-		return err
-	}
-
-	err = checkAllocatable(clientset, nodeName)
+func removeNotReadyTaint(ctx context.Context, clientset kubernetes.Interface, node *corev1.Node) error {
+	err := checkAllocatable(ctx, clientset, node.Name)
 	if err != nil {
 		return err
 	}
@@ -923,16 +985,16 @@ func removeNotReadyTaint(clientset kubernetes.Interface) error {
 		return err
 	}
 
-	_, err = clientset.CoreV1().Nodes().Patch(context.Background(), nodeName, k8stypes.JSONPatchType, patch, metav1.PatchOptions{})
+	_, err = clientset.CoreV1().Nodes().Patch(ctx, node.Name, k8stypes.JSONPatchType, patch, metav1.PatchOptions{})
 	if err != nil {
 		return err
 	}
-	klog.InfoS("Removed taint(s) from local node", "node", nodeName)
+	klog.InfoS("Removed taint(s) from local node", "node", node.Name)
 	return nil
 }
 
-func checkAllocatable(clientset kubernetes.Interface, nodeName string) error {
-	csiNode, err := clientset.StorageV1().CSINodes().Get(context.Background(), nodeName, metav1.GetOptions{})
+func checkAllocatable(ctx context.Context, clientset kubernetes.Interface, nodeName string) error {
+	csiNode, err := clientset.StorageV1().CSINodes().Get(ctx, nodeName, metav1.GetOptions{})
 	if err != nil {
 		return fmt.Errorf("isAllocatableSet: failed to get CSINode for %s: %w", nodeName, err)
 	}



Test patch:
diff --git a/pkg/driver/node_test.go b/pkg/driver/node_test.go
index 1e29a3f3..62543d2e 100644
--- a/pkg/driver/node_test.go
+++ b/pkg/driver/node_test.go
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-//nolint:forcetypeassert
 package driver
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"reflect"
@@ -31,14 +31,15 @@ import (
 	"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/cloud/metadata"
 	"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/driver/internal"
 	"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/mounter"
-	"github.com/stretchr/testify/assert"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/api/storage/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8stypes "k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/fake"
 )
 
 func TestNewNodeService(t *testing.T) {
@@ -2498,85 +2499,62 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 	nodeName := "test-node-123"
 	testCases := []struct {
 		name      string
-		setup     func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error)
+		setup     func(t *testing.T, mockCtl *gomock.Controller) (kubernetes.Interface, *corev1.Node)
 		expResult error
 	}{
 		{
-			name: "failed to get node",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
+			name: "checkAllocatable returns error",
+			setup: func(t *testing.T, mockCtl *gomock.Controller) (kubernetes.Interface, *corev1.Node) {
 				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, _ := getNodeMock(mockCtl, nodeName, nil, errors.New("Failed to get node!"))
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
+				node := &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: nodeName,
+					},
 				}
-			},
-			expResult: errors.New("Failed to get node!"),
-		},
-		{
-			name: "no taints to remove",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
-				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, _ := getNodeMock(mockCtl, nodeName, &corev1.Node{}, nil)
 
+				mockClient := NewMockKubernetesClient(mockCtl)
 				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
+				mockClient.EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
 
 				csiNodesMock := NewMockCSINodeInterface(mockCtl)
 				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
 
-				count := int32(1)
-
-				mockCSINode := &v1.CSINode{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "test-node-123",
-					},
-					Spec: v1.CSINodeSpec{
-						Drivers: []v1.CSINodeDriver{
-							{
-								Name: DriverName,
-								Allocatable: &v1.VolumeNodeResources{
-									Count: &count,
-								},
-							},
-						},
-					},
-				}
-
 				csiNodesMock.EXPECT().
-					Get(gomock.Any(), gomock.Eq("test-node-123"), gomock.Any()).
-					Return(mockCSINode, nil).
+					Get(gomock.Any(), gomock.Eq(nodeName), gomock.Any()).
+					Return(nil, errors.New("failed to get CSINode")).
 					Times(1)
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
-				}
+				return mockClient, node
 			},
-			expResult: nil,
+			expResult: fmt.Errorf("isAllocatableSet: failed to get CSINode for %s: failed to get CSINode", nodeName),
 		},
 		{
-			name: "failed to patch node",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
+			name: "no taints to remove",
+			setup: func(t *testing.T, mockCtl *gomock.Controller) (kubernetes.Interface, *corev1.Node) {
 				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, mockNode := getNodeMock(mockCtl, nodeName, &corev1.Node{
+
+				node := &corev1.Node{
 					ObjectMeta: metav1.ObjectMeta{
 						Name: nodeName,
 					},
 					Spec: corev1.NodeSpec{
 						Taints: []corev1.Taint{
 							{
-								Key:    AgentNotReadyNodeTaintKey,
+								Key:    "some-other-taint",
+								Effect: corev1.TaintEffectNoSchedule,
+							},
+							{
+								Key:    "another-taint",
 								Effect: corev1.TaintEffectNoExecute,
 							},
 						},
 					},
-				}, nil)
+				}
 
+				mockClient := NewMockKubernetesClient(mockCtl)
 				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
+				mockClient.EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
 
 				csiNodesMock := NewMockCSINodeInterface(mockCtl)
 				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
@@ -2589,8 +2567,7 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 					Spec: v1.CSINodeSpec{
 						Drivers: []v1.CSINodeDriver{
 							{
-								Name:   DriverName,
-								NodeID: nodeName,
+								Name: DriverName,
 								Allocatable: &v1.VolumeNodeResources{
 									Count: &count,
 								},
@@ -2604,23 +2581,16 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 					Return(mockCSINode, nil).
 					Times(1)
 
-				mockNode.EXPECT().
-					Patch(gomock.Any(), gomock.Eq(nodeName), gomock.Any(), gomock.Any(), gomock.Any()).
-					Return(nil, errors.New("Failed to patch node!")).
-					Times(1)
-
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
-				}
+				return mockClient, node
 			},
-			expResult: errors.New("Failed to patch node!"),
+			expResult: nil,
 		},
 		{
-			name: "success",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
+			name: "successfully removes taint",
+			setup: func(t *testing.T, mockCtl *gomock.Controller) (kubernetes.Interface, *corev1.Node) {
 				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, mockNode := getNodeMock(mockCtl, nodeName, &corev1.Node{
+
+				node := &corev1.Node{
 					ObjectMeta: metav1.ObjectMeta{
 						Name: nodeName,
 					},
@@ -2630,12 +2600,17 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 								Key:    AgentNotReadyNodeTaintKey,
 								Effect: corev1.TaintEffectNoSchedule,
 							},
+							{
+								Key:    "some-other-taint",
+								Effect: corev1.TaintEffectNoExecute,
+							},
 						},
 					},
-				}, nil)
+				}
+				mockClient := NewMockKubernetesClient(mockCtl)
 
 				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
+				mockClient.EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
 
 				csiNodesMock := NewMockCSINodeInterface(mockCtl)
 				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
@@ -2648,8 +2623,7 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 					Spec: v1.CSINodeSpec{
 						Drivers: []v1.CSINodeDriver{
 							{
-								Name:   DriverName,
-								NodeID: nodeName,
+								Name: DriverName,
 								Allocatable: &v1.VolumeNodeResources{
 									Count: &count,
 								},
@@ -2663,208 +2637,145 @@ func TestRemoveNotReadyTaint(t *testing.T) {
 					Return(mockCSINode, nil).
 					Times(1)
 
-				mockNode.EXPECT().
-					Patch(gomock.Any(), gomock.Eq(nodeName), gomock.Any(), gomock.Any(), gomock.Any()).
-					Return(nil, nil).
+				coreV1Mock := NewMockCoreV1Interface(mockCtl)
+				mockClient.EXPECT().CoreV1().Return(coreV1Mock).AnyTimes()
+
+				nodesMock := NewMockNodeInterface(mockCtl)
+				coreV1Mock.EXPECT().Nodes().Return(nodesMock).Times(1)
+
+				nodesMock.EXPECT().
+					Patch(
+						gomock.Any(),
+						gomock.Eq(nodeName),
+						gomock.Eq(k8stypes.JSONPatchType),
+						gomock.Any(),
+						gomock.Any(),
+					).
+					Return(node, nil).
 					Times(1)
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
-				}
+				return mockClient, node
 			},
 			expResult: nil,
 		},
-		{
-			name: "failed to get CSINode",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
-				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, _ := getNodeMock(mockCtl, nodeName, &corev1.Node{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: nodeName,
-					},
-					Spec: corev1.NodeSpec{
-						Taints: []corev1.Taint{
-							{
-								Key:    AgentNotReadyNodeTaintKey,
-								Effect: corev1.TaintEffectNoSchedule,
-							},
-						},
-					},
-				}, nil)
-
-				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
+	}
 
-				csiNodesMock := NewMockCSINodeInterface(mockCtl)
-				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			mockCtl := gomock.NewController(t)
+			defer mockCtl.Finish()
 
-				csiNodesMock.EXPECT().
-					Get(gomock.Any(), gomock.Eq(nodeName), gomock.Any()).
-					Return(nil, errors.New("Failed to get CSINode")).
-					Times(1)
+			client, node := tc.setup(t, mockCtl)
+			result := removeNotReadyTaint(t.Context(), client, node)
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
+			if tc.expResult == nil {
+				if result != nil {
+					t.Fatalf("expected no error, got %v", result)
 				}
-			},
-			expResult: fmt.Errorf("isAllocatableSet: failed to get CSINode for %s: Failed to get CSINode", nodeName),
-		},
-		{
-			name: "allocatable value not set for driver on node",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
-				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, _ := getNodeMock(mockCtl, nodeName, &corev1.Node{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: nodeName,
-					},
-					Spec: corev1.NodeSpec{
-						Taints: []corev1.Taint{
-							{
-								Key:    AgentNotReadyNodeTaintKey,
-								Effect: corev1.TaintEffectNoSchedule,
-							},
-						},
-					},
-				}, nil)
-
-				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
-
-				csiNodesMock := NewMockCSINodeInterface(mockCtl)
-				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
-
-				mockCSINode := &v1.CSINode{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: nodeName,
-					},
-					Spec: v1.CSINodeSpec{
-						Drivers: []v1.CSINodeDriver{
-							{
-								Name:   DriverName,
-								NodeID: nodeName,
-							},
-						},
-					},
+			} else {
+				if result == nil {
+					t.Fatalf("expected error %v, got nil", tc.expResult)
+				}
+				if result.Error() != tc.expResult.Error() {
+					t.Fatalf("expected error %v, got %v", tc.expResult, result)
 				}
+			}
+		})
+	}
+}
 
-				csiNodesMock.EXPECT().
-					Get(gomock.Any(), gomock.Eq(nodeName), gomock.Any()).
-					Return(mockCSINode, nil).
-					Times(1)
+func TestStartNotReadyTaintWatcher(t *testing.T) {
+	const nodeName = "ip-10-0-0-1"
+	t.Setenv("CSI_NODE_NAME", nodeName)
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
-				}
-			},
-			expResult: fmt.Errorf("isAllocatableSet: allocatable value not set for driver on node %s", nodeName),
+	tests := []struct {
+		name          string
+		seedWithTaint bool
+		addTaintLater bool
+		expectPatched bool
+	}{
+		{
+			name:          "taint-present-at-startup",
+			seedWithTaint: true,
+			expectPatched: true,
 		},
 		{
-			name: "driver not found on node",
-			setup: func(t *testing.T, mockCtl *gomock.Controller) func() (kubernetes.Interface, error) {
-				t.Helper()
-				t.Setenv("CSI_NODE_NAME", nodeName)
-				getNodeMock, _ := getNodeMock(mockCtl, nodeName, &corev1.Node{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: nodeName,
-					},
-					Spec: corev1.NodeSpec{
-						Taints: []corev1.Taint{
-							{
-								Key:    AgentNotReadyNodeTaintKey,
-								Effect: corev1.TaintEffectNoSchedule,
-							},
-						},
-					},
-				}, nil)
-
-				storageV1Mock := NewMockStorageV1Interface(mockCtl)
-				getNodeMock.(*MockKubernetesClient).EXPECT().StorageV1().Return(storageV1Mock).AnyTimes()
+			name:          "taint-added-later",
+			addTaintLater: true,
+			expectPatched: true,
+		},
+	}
 
-				csiNodesMock := NewMockCSINodeInterface(mockCtl)
-				storageV1Mock.EXPECT().CSINodes().Return(csiNodesMock).Times(1)
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}}
+			if tc.seedWithTaint {
+				node.Spec.Taints = []corev1.Taint{{
+					Key:    AgentNotReadyNodeTaintKey,
+					Value:  "true",
+					Effect: corev1.TaintEffectNoSchedule,
+				}}
+			}
 
-				mockCSINode := &v1.CSINode{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: nodeName,
+			count := int32(1)
+			csiNode := &v1.CSINode{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: nodeName,
+				},
+				Spec: v1.CSINodeSpec{
+					Drivers: []v1.CSINodeDriver{
+						{
+							Name: DriverName,
+							Allocatable: &v1.VolumeNodeResources{
+								Count: &count,
+							},
+						},
 					},
-					Spec: v1.CSINodeSpec{},
-				}
+				},
+			}
 
-				csiNodesMock.EXPECT().
-					Get(gomock.Any(), gomock.Eq(nodeName), gomock.Any()).
-					Return(mockCSINode, nil).
-					Times(1)
+			client := fake.NewSimpleClientset(node, csiNode)
 
-				return func() (kubernetes.Interface, error) {
-					return getNodeMock, nil
-				}
-			},
-			expResult: fmt.Errorf("isAllocatableSet: driver not found on node %s", nodeName),
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			mockCtl := gomock.NewController(t)
-			defer mockCtl.Finish()
+			startNotReadyTaintWatcher(client, 1*time.Second)
 
-			k8sClientGetter := tc.setup(t, mockCtl)
-			client, err := k8sClientGetter()
-			if err != nil {
-				t.Fatalf("Unexpected error: %v", err)
+			if tc.addTaintLater {
+				time.AfterFunc(5*time.Millisecond, func() {
+					nodeCopy := node.DeepCopy()
+					nodeCopy.Spec.Taints = []corev1.Taint{{
+						Key:    AgentNotReadyNodeTaintKey,
+						Value:  "true",
+						Effect: corev1.TaintEffectNoSchedule,
+					}}
+					_, _ = client.CoreV1().Nodes().
+						Update(t.Context(), nodeCopy, metav1.UpdateOptions{})
+				})
 			}
-			result := removeNotReadyTaint(client)
 
-			if (result == nil) != (tc.expResult == nil) {
-				t.Fatalf("expected %v, got %v", tc.expResult, result)
+			patched := false
+			err := wait.PollUntilContextTimeout(
+				t.Context(),
+				5*time.Millisecond,
+				100*time.Millisecond,
+				true,
+				func(ctx context.Context) (bool, error) {
+					updated, _ := client.CoreV1().Nodes().
+						Get(ctx, nodeName, metav1.GetOptions{})
+					for _, taint := range updated.Spec.Taints {
+						if taint.Key == AgentNotReadyNodeTaintKey {
+							return false, nil // still tainted
+						}
+					}
+					patched = true
+					return true, nil
+				},
+			)
+
+			if tc.expectPatched && (err != nil || !patched) {
+				t.Fatalf("taint was not removed before timeout: %v", err)
 			}
-			if result != nil && tc.expResult != nil {
-				if result.Error() != tc.expResult.Error() {
-					t.Fatalf("Expected error message `%v`, got `%v`", tc.expResult.Error(), result.Error())
-				}
+			if !tc.expectPatched && patched {
+				t.Fatalf("taint unexpectedly removed")
 			}
 		})
 	}
 }
-
-func TestRemoveTaintInBackground(t *testing.T) {
-	t.Run("Successful taint removal", func(t *testing.T) {
-		mockRemovalCount := 0
-		mockRemovalFunc := func(_ kubernetes.Interface) error {
-			mockRemovalCount += 1
-			if mockRemovalCount == 3 {
-				return nil
-			} else {
-				return errors.New("Taint removal failed!")
-			}
-		}
-		removeTaintInBackground(nil, taintRemovalBackoff, mockRemovalFunc)
-		assert.Equal(t, 3, mockRemovalCount)
-	})
-
-	t.Run("Retries exhausted", func(t *testing.T) {
-		mockRemovalCount := 0
-		mockRemovalFunc := func(_ kubernetes.Interface) error {
-			mockRemovalCount += 1
-			return errors.New("Taint removal failed!")
-		}
-		removeTaintInBackground(nil, wait.Backoff{
-			Steps:    5,
-			Duration: 1 * time.Millisecond,
-		}, mockRemovalFunc)
-		assert.Equal(t, 5, mockRemovalCount)
-	})
-}
-
-func getNodeMock(mockCtl *gomock.Controller, nodeName string, returnNode *corev1.Node, returnError error) (kubernetes.Interface, *MockNodeInterface) {
-	mockClient := NewMockKubernetesClient(mockCtl)
-	mockCoreV1 := NewMockCoreV1Interface(mockCtl)
-	mockNode := NewMockNodeInterface(mockCtl)
-
-	mockClient.EXPECT().CoreV1().Return(mockCoreV1).MinTimes(1)
-	mockCoreV1.EXPECT().Nodes().Return(mockNode).MinTimes(1)
-	mockNode.EXPECT().Get(gomock.Any(), gomock.Eq(nodeName), gomock.Any()).Return(returnNode, returnError).MinTimes(1)
-
-	return mockClient, mockNode
-}