hyperledger/fabric · error
only one transaction action is supported, %d were present
Error message
only one transaction action is supported, %d were present
What it means
The transaction inside the envelope contains multiple (or zero) TransactionActions. Fabric's endorser transaction format parsed by this library supports exactly one chaincode action per transaction, so any other count is rejected. This is a deliberate structural constraint of this parser, not a generic protobuf failure.
Source
Thrown at core/tx/endorser/parser.go:64
hdrExt, err := protoutil.UnmarshalChaincodeHeaderExtension(
txenv.ChannelHeader.Extension,
)
if err != nil {
return nil, err
}
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 {View on GitHub (pinned to 2736b63f8f)
Solutions
- Split multi-action transactions into one transaction per action before submitting/parsing.
- Regenerate the transaction with the standard Fabric SDK/protoutil path, which produces exactly one action.
- If parsing, filter client-side: only feed envelopes whose Transaction.Actions length is 1 to this API.
Example fix
// before
tx.Actions = []*common.TransactionAction{action1, action2}
// after
tx.Actions = []*common.TransactionAction{action1} // one action per tx; submit action2 separately Defensive patterns
Strategy: validation
Validate before calling
func hasSingleAction(txBytes []byte) bool {
tx := &common.Transaction{}
if proto.Unmarshal(txBytes, tx) != nil {
return false
}
return len(tx.GetActions()) == 1
} Type guard
func isSingleActionTx(tx *common.Transaction) bool {
return tx != nil && len(tx.GetActions()) == 1 && tx.GetActions()[0] != nil
} Try / catch
tx, err := parser.UnmarshalEndorserTxAndValidate(env)
if err != nil {
var af *feeders.Errors
if strings.Contains(err.Error(), "only one transaction action is supported") {
// split or reject the multi-action transaction
return fmt.Errorf("unsupported tx: %w", err)
}
return err
} Prevention
- Enforce one-action-per-transaction in any custom transaction builder.
- Submit each chaincode invocation as its own transaction.
- Pre-validate Transaction.Actions length client-side before envelope assembly.
When it happens
Trigger: UnmarshalEndorserTxAndValidate receives an envelope whose Transaction.Actions slice has length != 1 — typically a multi-action transaction produced by custom tooling, or a Transaction with no actions at all.
Common situations: Older/custom Fabric SDKs or chaincode tooling that batched multiple actions into one transaction; programmatically assembled transactions with duplicate actions; corrupted protobuf that deserialized with zero actions.
Related errors
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/836359c9e90682c2.
Report an issue: GitHub.