hyperledger/fabric · error

nil action

Error message

nil action

What it means

The single TransactionAction present in the transaction is nil. This happens when the protobuf Transaction serialized a null action pointer in its Actions slice. The parser checks the element after selecting it to avoid a nil-pointer dereference downstream.

Source

Thrown at core/tx/endorser/parser.go:70

	}

	if len(txenv.Data) == 0 {
		return nil, errors.New("nil payload data")
	}

	tx, err := protoutil.UnmarshalTransaction(txenv.Data)
	if err != nil {
		return nil, err
	}

	if len(tx.GetActions()) != 1 {
		return nil, errors.Errorf("only one transaction action is supported, %d were present", len(tx.GetActions()))
	}

	txAction := tx.GetActions()[0]

	if txAction == nil {
		return nil, errors.New("nil action")
	}

	if len(txAction.Payload) == 0 {
		return nil, errors.New("empty ChaincodeActionPayload")
	}

	ccActionPayload, err := protoutil.UnmarshalChaincodeActionPayload(txAction.Payload)
	if err != nil {
		return nil, err
	}

	if ccActionPayload.Action == nil {
		return nil, errors.New("nil ChaincodeEndorsedAction")
	}

	if len(ccActionPayload.Action.ProposalResponsePayload) == 0 {
		return nil, errors.New("empty ProposalResponsePayload")
	}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Rebuild the transaction ensuring each TransactionAction is a fully populated struct before marshaling.
  2. Add a client-side check that tx.Actions[0] != nil before creating the envelope.
  3. Trace the code that produced the transaction and fix the nil pointer assignment.

Example fix

// before
var action *common.TransactionAction // left nil
tx.Actions = []*common.TransactionAction{action}
// after
action := &common.TransactionAction{Payload: chaincodeActionPayloadBytes}
tx.Actions = []*common.TransactionAction{action}
Defensive patterns

Strategy: type-guard

Validate before calling

func hasPopulatedAction(tx *common.Transaction) bool {
    actions := tx.GetActions()
    return len(actions) > 0 && actions[0] != nil
}

Type guard

func isNotNilAction(a *common.TransactionAction) bool {
    return a != nil
}

Try / catch

tx, err := parser.UnmarshalEndorserTxAndValidate(env)
if err != nil {
    if strings.Contains(err.Error(), "nil action") {
        // transaction was built with a nil action: reject/regenerate
        return ErrBadTransaction
    }
    return err
}

Prevention

When it happens

Trigger: UnmarshalEndorserTxAndValidate is given an envelope whose Transaction.Actions[0] deserialized to nil — e.g. an action pointer appended to the slice without being populated before marshaling.

Common situations: Hand-built transactions where append(tx.Actions, nil) or an uninitialized struct pointer was marshaled; bugs in custom transaction-generation tooling.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/8608b2d733f445ae. Report an issue: GitHub.