temporalio / temporalio/temporal
SyncState() ignores remainingAttempt — no retry limit enforced unlike Resend()
@yux0 is already working on this.
Since Jun 11, 2026.
- Dominant language
- Go
- Stars
- 23.2k
- Forks
- 1.9k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 228
Description
Summary
SyncState() in executable_task.go accepts a remainingAttempt
parameter but never checks or decrements it, unlike Resend() which
correctly enforces the retry limit.
Root Cause
Resend() correctly handles remainingAttempt:
remainingAttempt--
if remainingAttempt < 0 {
return false, ErrResendAttemptExceeded
}
SyncState() ignores it entirely:
// TODO: check & update remainingAttempt
// ... proceeds without any retry limit check
Impact
SyncState()can retry indefinitely on certain error types- Callers always pass
ResendAttempt = 2as a constant - The retry limit is never enforced for sync state operations
- Inconsistent behavior between
SyncState()andResend() - Could cause excessive load on source cluster during sync failures
Affected Callers
All callers pass ResendAttempt constant (= 2) but it's never used:
executable_sync_versioned_transition_task.go:154executable_verify_versioned_transition_task.go:288executable_workflow_state_task.go:149, 181executable_sync_hsm_task.go:154
Suggested Fix
Apply the same pattern as Resend():
func (e *ExecutableTaskImpl) SyncState(
ctx context.Context,
syncStateErr *serviceerrors.SyncState,
remainingAttempt int,
) (bool, error) {
remainingAttempt--
if remainingAttempt < 0 {
e.Logger.Error("sync state attempts exceeded", ...)
return false, ErrResendAttemptExceeded
}
// ... rest of function
}
References
service/history/replication/executable_task.go:702(TODO)service/history/replication/executable_task.go:425-440(Resend reference)service/history/replication/executable_task.go:47(ResendAttempt = 2)
Expected Behavior
SyncState() should enforce the retry limit passed via
remainingAttempt parameter, stopping retries and returning
ErrResendAttemptExceeded when the limit is reached —
consistent with how Resend() handles the same parameter.
Actual Behavior
SyncState() ignores the remainingAttempt parameter entirely.
Despite callers always passing ResendAttempt (= 2), no retry
limit is enforced. The function can retry indefinitely on
certain error types, unlike Resend() which correctly
decrements and checks the limit.
Steps to Reproduce the Problem
- Trigger a replication sync state operation that fails
repeatedly (e.g. source cluster unavailable) - Observe SyncState() retrying without limit
- Compare with Resend() which stops after 2 attempts
and returns ErrResendAttemptExceeded
Specifications
- Version: latest main
- Platform: any
- Affected file: service/history/replication/executable_task.go:702
- Related: ResendAttempt constant (line 47), ErrResendAttemptExceeded (line 51)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.