Repository navigation
Snapshot stream rejects the 32-character digest issued for multi-document namespaces #6590
Description
Problem
EvaluationSnapshotNamespaceStream rejects every non-empty digest that isn't 40 characters long (client/server.go:118-120). But when a namespace is built from more than one document, the snapshot digest the server issues is 32 characters. For example:
- the namespace is split across two files matched by
flipt.yml, or - one file holds several YAML documents for the same namespace.
In that case snapshot.go:612-622 chains the per-file sha1s and truncates the result to 32 characters. TestEtagWithFewDocs asserts that behavior.
A client that resumes the stream by sending back the digest it received gets InvalidArgument ("must be a 40-character string"). The proto documents the field as the "Last snapshot digest the client has" (evaluationv2.proto:143), so sending it back is the intended use.
No client in this repo sends digest today, so only external clients that use stream resume are affected.
Reproduce
This test fails with invalid field digest: must be a 40-character string for the 32-character digest:
internal/server/evaluation/client/digest_test.go
package client
import (
"testing"
"testing/fstest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.flipt.io/flipt/internal/server/environments"
"go.flipt.io/flipt/internal/server/evaluation"
storagefs "go.flipt.io/flipt/internal/storage/fs"
rpcevaluation "go.flipt.io/flipt/rpc/v2/evaluation"
"go.uber.org/zap/zaptest"
)
func TestEvaluationSnapshotNamespaceStream_MultiDocumentDigest(t *testing.T) {
logger := zaptest.NewLogger(t)
src := fstest.MapFS{
"a/features.yaml": {Data: []byte("namespace:\n key: default\nflags:\n- key: flag_a\n name: A\n type: BOOLEAN_FLAG_TYPE\n enabled: true\n")},
"b/features.yaml": {Data: []byte("namespace:\n key: default\nflags:\n- key: flag_b\n name: B\n type: BOOLEAN_FLAG_TYPE\n enabled: true\n")},
}
snap, err := storagefs.SnapshotFromPaths(logger, src, []string{"a/features.yaml", "b/features.yaml"})
require.NoError(t, err)
nsSnap, err := snap.EvaluationNamespaceSnapshot(t.Context(), "default")
require.NoError(t, err)
require.Len(t, nsSnap.Flags, 2)
t.Logf("digest=%q len=%d", nsSnap.Digest, len(nsSnap.Digest))
require.Len(t, nsSnap.Digest, 32)
mockEnv := environments.NewMockEnvironment(t)
envStore := evaluation.NewMockEnvironmentStore(t)
mockEnv.On("Key").Return("env-key").Maybe()
envStore.On("Get", mock.Anything, "env-key").Return(mockEnv, nil).Maybe()
mockEnv.On("EvaluationNamespaceSnapshotSubscribe", mock.Anything, "default", mock.Anything).Return(&fakeCloser{}, nil).Run(func(args mock.Arguments) {
ch := args.Get(2).(chan<- *rpcevaluation.EvaluationNamespaceSnapshot)
go func() { ch <- nsSnap; close(ch) }()
}).Maybe()
stream := &mockStream{ctx: t.Context()}
stream.On("Send", mock.Anything).Return(nil).Maybe()
s := NewServer(logger, envStore)
d := nsSnap.Digest
err = s.EvaluationSnapshotNamespaceStream(&rpcevaluation.EvaluationNamespaceSnapshotStreamRequest{
EnvironmentKey: "env-key", Key: "default", Digest: &d,
}, stream)
// Expected behavior: resuming with the server-issued digest is accepted and the unchanged snapshot is not resent.
assert.NoError(t, err)
assert.Empty(t, stream.sent)
}Suggested fix
Accept any digest the server can issue: relax the check to 32 or 40 characters, or just a hex string with a sane maximum. The alternative is to stop truncating the chained digest. Add a stream test that uses a digest from a real multi-document snapshot.
Activity
github-actions commentedon Sep 24, 2026
Issue Health Check: Well scoped
Type: Bug report
A precise bug report identifying a validation mismatch where EvaluationSnapshotNamespaceStream rejects 32-character digests that the server itself issues for multi-document namespaces. It includes exact code links, a complete reproduction test, and a suggested fix.
Thanks — this issue looks well scoped and ready for the maintainers to evaluate.
Need help with Flipt? Support the project through GitHub Sponsors or learn about Flipt Pro.