set_retry Deprecated: Use set_timeout or set_interval Instead
The set_retry, cancel_retry, and RetryResult APIs are deprecated. They will be removed in ESPHome 2026.8.0. Use set_timeout or set_interval instead.
This is a breaking change for external components in ESPHome 2026.2.0 and later.
Background
PR #13845: Deprecate set_retry, cancel_retry, and RetryResult
The set_retry API has several problems that make it unsuitable for ESPHome's embedded environment:
-
Hidden heap allocation. Every
set_retrycall does astd::make_shared<RetryArgs>(), allocating on the heap. This is invisible to component authors and contributes to fragmentation on devices that run for months. -
Wasted flash and RAM. The retry machinery —
RetryArgsstruct,retry_handler(),set_retry_common_(), multiple overloads, and the heavierstd::function<RetryResult(uint8_t)>template instantiation — is compiled into every firmware whether or not any component uses it. -
Confusing immediate-first-execution semantics.
set_retryexecutes the callback immediately (delay=0) before waitinginitial_wait_timefor subsequent attempts. Callers expected it to wait first, likeset_intervaldoes. -
The abstraction didn't match real usage. All internal callers were using
set_retryas a fixed-interval poll (backoff factor 1.0, always returningRETRY). These are trivially served byset_interval+ counter or chainedset_timeout.
All internal usage has been removed in companion PRs: #13841 (lps22), #13842 (ms8607), #13843 (speaker), #13844 (esp32_hosted).
What's Changing
The entire set_retry / cancel_retry / RetryResult API is marked ESPDEPRECATED. Using any of these will produce compiler warnings now. The API will be removed in 2026.8.0.
// All of these are deprecated:
this->set_retry("name", 100, 5, [](uint8_t) { return RetryResult::RETRY; });
this->set_retry(100, 5, [](uint8_t) { return RetryResult::RETRY; });
this->cancel_retry("name");
RetryResult result = RetryResult::DONE;
Who This Affects
External components that call set_retry or cancel_retry, or use RetryResult.
Standard YAML configurations are not affected.
Migration Guide
Pattern 1: Fixed-interval polling (most common)
The most common set_retry usage is polling at a fixed interval until a condition is met. Replace with set_interval and a counter:
// Before
void MyComponent::update() {
this->trigger_measurement();
this->set_retry("read", 5, 10, [this](uint8_t remaining) {
if (this->data_ready()) {
this->read_data();
return RetryResult::DONE;
}
return RetryResult::RETRY;
});
}
// After
static constexpr uint32_t INTERVAL_READ = 0; // numeric ID
void MyComponent::update() {
this->trigger_measurement();
this->read_attempts_remaining_ = 10;
this->set_interval(INTERVAL_READ, 5, [this]() {
if (this->data_ready()) {
this->cancel_interval(INTERVAL_READ);
this->read_data();
} else if (--this->read_attempts_remaining_ == 0) {
this->cancel_interval(INTERVAL_READ);
}
});
}
Note: set_interval waits before the first execution, unlike set_retry which fired immediately. This is usually the desired behavior — it gives the hardware time to complete the operation before the first read attempt.
Pattern 2: Retry with backoff
For retries with increasing delays, use chained set_timeout:
// Before
this->set_retry("reset", 5, 3, [this](uint8_t remaining) {
if (this->try_reset()) {
return RetryResult::DONE;
}
return RetryResult::RETRY;
}, 5.0f); // backoff factor
// After
void MyComponent::setup() {
this->reset_attempts_remaining_ = 3;
this->reset_interval_ = 5;
this->try_reset_();
}
void MyComponent::try_reset_() {
if (this->try_reset()) {
// Success
return;
}
if (--this->reset_attempts_remaining_ > 0) {
uint32_t delay = this->reset_interval_;
this->reset_interval_ *= 5; // backoff factor
this->set_timeout("reset", delay, [this]() { this->try_reset_(); });
} else {
this->mark_failed();
}
}
Pattern 3: Wait for state transition
For waiting until something reaches a target state:
// Before
this->set_retry("wait_stop", 50, 3, [this](uint8_t remaining) {
if (this->state_ == STATE_STOPPED) {
this->on_stopped();
return RetryResult::DONE;
}
return RetryResult::RETRY;
});
// After
this->wait_attempts_remaining_ = 3;
this->set_interval("wait_stop", 50, [this]() {
if (this->state_ == STATE_STOPPED) {
this->cancel_interval("wait_stop");
this->on_stopped();
} else if (--this->wait_attempts_remaining_ == 0) {
this->cancel_interval("wait_stop");
}
});
Supporting Multiple ESPHome Versions
No version guard is needed. set_interval and set_timeout are available in all ESPHome versions, so migrated code works everywhere.
Timeline
- ESPHome 2026.2.0 (February 2026): Deprecation warnings active
- ESPHome 2026.8.0 (August 2026):
set_retry,cancel_retry, andRetryResultremoved
Finding Code That Needs Updates
# Find set_retry usage
grep -rn 'set_retry' your_component/
grep -rn 'cancel_retry' your_component/
grep -rn 'RetryResult' your_component/
Questions?
If you have questions about migrating your external component, please ask in:
- ESPHome Discord - #devs channel
- ESPHome GitHub Discussions
Related Documentation
- PR #13845: Deprecate set_retry
- PR #13841: lps22 migration
- PR #13842: ms8607 migration
- PR #13843: speaker migration
- PR #13844: esp32_hosted migration
Comments
Feel free to leave a comment here to discuss this post wth others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.