t8y2/dbx · error

ETCD_INVALID_REVISION

ETCD_INVALID_REVISION

Error message

ETCD_INVALID_REVISION: revision was already compacted at 

What it means

compact first probes the requested revision with a count-only Get at that revision; if etcd replies rpctypes.ErrCompacted, the revision has already been compacted server-side. When compactedRevisionOf succeeds in reading the current compacted revision, the error message includes it: "ETCD_INVALID_REVISION: revision was already compacted at <N>". This distinguishes 'too old' from the newer-than-current case handled separately.

Source

Thrown at agents/drivers/etcd-go/maintenance.go:32

	return json.Unmarshal(raw, target)
}

func (s *etcdSession) compact(params map[string]json.RawMessage) (any, error) {
	revision, err := requiredPositiveLong(params, "revision")
	if err != nil {
		return nil, err
	}
	client, err := s.activeClient()
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeoutSeconds*time.Second)
	_, err = client.Get(ctx, "\x00", clientv3.WithRange("\x00"), clientv3.WithCountOnly(), clientv3.WithRev(revision))
	cancel()
	if err != nil {
		if errors.Is(err, rpctypes.ErrCompacted) {
			if compacted, ok := compactedRevisionOf(client, "\x00"); ok {
				return nil, errors.New("ETCD_INVALID_REVISION: revision was already compacted at " + longString(compacted))
			}
			return nil, errors.New("ETCD_INVALID_REVISION: revision was already compacted")
		}
		return nil, err
	}
	currentCtx, currentCancel := context.WithTimeout(context.Background(), rpcTimeoutSeconds*time.Second)
	current, err := client.Get(currentCtx, "\x00", clientv3.WithRange("\x00"), clientv3.WithCountOnly())
	currentCancel()
	if err != nil {
		return nil, err
	}
	if revision > current.Header.Revision {
		return nil, errors.New("ETCD_INVALID_REVISION: revision is newer than the current revision")
	}
	compactCtx, compactCancel := context.WithTimeout(context.Background(), rpcTimeoutSeconds*time.Second)
	defer compactCancel()
	if _, err := client.Compact(compactCtx, revision); err != nil {
		return nil, err

View on GitHub (pinned to c0390bff16)

Solutions

  1. Parse the compacted revision from the message and only request revisions > that value.
  2. Read the current revision (e.g. a Get's header.revision) and compact from current minus your desired history window.
  3. Make the compaction job idempotent: treat 'already compacted at N' as success and skip.
  4. Coordinate with etcd's --auto-compaction-mode/retention so manual compaction is rarely needed.

Example fix

// before: stale revision from hours ago
agent.call("compact", {"revision": 42})

// after: derive from current revision
head, _ := agent.call("get", {"key": "cfg/a"})
rev := head["revision"].(int64) - 1000 // keep window
if rev > 42 { agent.call("compact", {"revision": rev}) }
Defensive patterns

Strategy: validation

Validate before calling

head, _ := agent.call("get", map[string]any{"key": "\x00", "countOnly": true})
current := head["revision"].(int64)
if targetRev < current-retainWindow { /* already inside compacted region risk */ }
// only compact revisions you recently observed on THIS cluster

Try / catch

_, err := agent.call("compact", map[string]any{"revision": rev})
if err != nil && strings.Contains(err.Error(), "already compacted") {
    return nil // idempotent: compaction already progressed past rev
}

Prevention

When it happens

Trigger: Calling compact (via handle) with a revision lower than the cluster's current compacted revision; maintenance.go:32 returns this exact message after compactedRevisionOf reports the compact point.

Common situations: Re-running a compaction script that already succeeded; compacting to a revision captured long ago while periodic auto-compaction advanced the compacted frontier; coordinating multiple operators who compact concurrently.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/5d55150fd83e2245. Report an issue: GitHub.