slackhq/nebula · error
ErrInitiateAlreadyCalled
ErrInitiateAlreadyCalled
Error message
initiate already called
What it means
ErrInitiateAlreadyCalled is returned when Machine.Initiate is invoked a second time. The Noise handshake state must be at message index 0 to initiate; once Initiate has run, the handshake is in progress and cannot be re-initiated.
Source
Thrown at handshake/errors.go:7
package handshake
import "errors"
var (
ErrInitiateOnResponder = errors.New("initiate called on responder")
ErrInitiateAlreadyCalled = errors.New("initiate already called")
ErrInitiateNotCalled = errors.New("initiate must be called before ProcessPacket for initiators")
ErrPacketTooShort = errors.New("packet too short")
ErrPublicKeyMismatch = errors.New("public key mismatch between certificate and handshake")
ErrIncompleteHandshake = errors.New("handshake completed without receiving required content")
ErrMachineFailed = errors.New("handshake machine has failed")
ErrUnknownSubtype = errors.New("unknown handshake subtype")
ErrMissingContent = errors.New("expected handshake content but message was empty")
ErrUnexpectedContent = errors.New("received unexpected handshake content")
ErrInvalidRemoteIndex = errors.New("peer sent an invalid index in handshake payload")
ErrIndexAllocation = errors.New("failed to allocate local index")
ErrNoCredential = errors.New("no handshake credential available for cert version")
ErrAsymmetricCipherKeys = errors.New("noise produced only one cipher key")
ErrMultiMessageUnsupported = errors.New("multi-message handshake patterns are not yet supported by the manager")
ErrSubtypeMismatch = errors.New("packet subtype does not match handshake machine subtype")
)
View on GitHub (pinned to dd8f660c0a)
Solutions
- Create a fresh Machine for each handshake attempt instead of reusing one.
- Only call Initiate once per Machine; resume an in-progress handshake with ProcessPacket.
- Guard the call site: check MessageIndex()==0 or a local 'initiated' flag before calling Initiate.
Example fix
// before
if err != nil {
packet, err = m.Initiate(nil) // reuse after failure
}
// after
if err != nil {
m = handshake.NewMachine(cs, true) // fresh machine per attempt
packet, err = m.Initiate(nil)
} Defensive patterns
Strategy: try-catch
Validate before calling
if m.hs != nil && m.hs.MessageIndex() != 0 {
return errors.New("handshake already initiated")
} Type guard
func canCallInitiate(m *handshake.Machine) bool {
return m.Result().Initiator && !m.Failed()
} Try / catch
packet, err := m.Initiate(payload)
if errors.Is(err, handshake.ErrInitiateAlreadyCalled) {
// reuse existing handshake state; do not retry Initiate
return nil
} Prevention
- Create a new Machine per handshake attempt
- Never call Initiate inside retry loops on a reused machine
- Track an 'initiated' boolean at the call site
- After a failed handshake, rebuild the machine from scratch
When it happens
Trigger: Calling Machine.Initiate more than once on the same machine — detected via m.hs.MessageIndex() != 0 in handshake/machine.go:177 — typically on retry logic that reuses the machine instead of rebuilding it.
Common situations: Retry loops that call Initiate again after a failed or timed-out handshake using the same Machine; reconnect logic reusing a machine whose handshake already started.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/770bc18b544bf18b.
Report an issue: GitHub.