Skip to content

fix: resolve secret refs created after store startup - #2459

Merged
yohamta0 merged 2 commits into
mainfrom
agent/refresh-secret-ref-index
Jul 30, 2026
Merged

fix: resolve secret refs created after store startup#2459
yohamta0 merged 2 commits into
mainfrom
agent/refresh-secret-ref-index

Conversation

@yohamta0

@yohamta0 yohamta0 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • fall back to the persisted secret collection when the process-local ref index misses
  • validate cached ref mappings against the current persisted secret before returning them
  • add regression coverage with two stores created before the secret is written

Root cause

The frontend and coordinator construct separate SecretStore instances over the same file-backed collection. Each instance has its own in-memory ref index, so a coordinator that started before a frontend-created secret treated an index miss as authoritative even though the secret was already persisted.

Impact

A running coordinator can resolve managed secrets created through the frontend/API without requiring a restart.

Testing

  • make fmt
  • make test TEST_TARGET="./internal/persis/store ./internal/service/coordinator"

Closes #2458


Summary by cubic

Fixes secret ref lookups when multiple SecretStore instances share the same persisted collection. Coordinators now resolve secrets created after startup without a restart, while preserving concurrent ref index updates.

  • Bug Fixes
    • In GetByRef, validate and evict stale byRef entries before returning.
    • On miss or stale entry, scan persistence via findByRef, then conditionally add to byRef to avoid clobbering concurrent mappings.
    • Added regression test for separate reader/writer SecretStore instances.

Written for commit 8b3cd39. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved secret lookups by reference when cached mappings are stale or missing.
    • Added automatic recovery from persisted data so valid secrets remain discoverable across store instances.
  • Tests

    • Added coverage verifying that reference-based lookups work across separate stores sharing the same storage.

Copilot AI review requested due to automatic review settings July 30, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SecretStore.GetByRef now validates cached mappings and falls back to persisted-record scanning when mappings are missing or stale. A test verifies lookup across two stores sharing the same collection.

Changes

Secret reference lookup

Layer / File(s) Summary
Persistent reference resolution and cross-store validation
internal/persis/store/secret.go, internal/persis/store/secret_test.go
GetByRef validates and removes stale index entries, findByRef scans persistence and repopulates the index, and the new test verifies lookup across separate store instances.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SecretStore
  participant byRef
  participant SecretCollection
  SecretStore->>byRef: Look up workspace/ref key
  SecretStore->>SecretCollection: Validate cached secret with GetByID
  SecretStore->>SecretCollection: Scan persisted records on cache miss
  SecretStore->>byRef: Store discovered secret ID
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code and test match #2458 by falling back to persistence, evicting stale refs, and adding two-store coverage.
Out of Scope Changes check ✅ Passed The changes stay focused on secret ref resolution and regression coverage, with no obvious unrelated edits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely summarizes the main fix: resolving secret references created after store startup.
Description check ✅ Passed The description covers summary, root cause, impact, testing, and linked issue, though it omits an explicit Changes section and checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/refresh-secret-ref-index

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yohamta0
yohamta0 marked this pull request as ready for review July 30, 2026 02:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/persis/store/secret.go (1)

405-420: 🚀 Performance & Scalability | 🔵 Trivial

Monitor the full-collection miss path.

findByRef decodes every persisted record for each unresolved reference. This is acceptable as a fallback, but can become an O(N) latency/CPU hotspot for large collections or repeated unknown-reference lookups. Add metrics around this path and consider a persisted lookup index if secret volume grows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/persis/store/secret.go` around lines 405 - 420, Instrument the
full-collection fallback in SecretStore.findByRef, recording metrics when
listAll succeeds but no matching secret is found, including collection-scan
activity and unresolved-reference outcomes. Keep the existing lookup behavior
unchanged, and use the repository’s established metrics conventions; leave
persisted lookup-index work out of this change unless an existing index
mechanism is already available.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/persis/store/secret.go`:
- Around line 173-179: Update the cache write in the method containing findByRef
so it does not overwrite a mapping created while the scan was in progress: under
s.mu, check whether s.byRef[rk] is still absent before assigning sec.ID.
Preserve concurrent newer mappings and keep the existing lookup error handling
unchanged.

---

Nitpick comments:
In `@internal/persis/store/secret.go`:
- Around line 405-420: Instrument the full-collection fallback in
SecretStore.findByRef, recording metrics when listAll succeeds but no matching
secret is found, including collection-scan activity and unresolved-reference
outcomes. Keep the existing lookup behavior unchanged, and use the repository’s
established metrics conventions; leave persisted lookup-index work out of this
change unless an existing index mechanism is already available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51f6ad1d-7988-4107-9587-c54b8845cf01

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd7eef and 8c78fa7.

📒 Files selected for processing (2)
  • internal/persis/store/secret.go
  • internal/persis/store/secret_test.go

Comment on lines +173 to +179
sec, err := s.findByRef(ctx, workspace, ref)
if err != nil {
return nil, err
}
return s.GetByID(ctx, id)
s.mu.Lock()
s.byRef[rk] = sec.ID
s.mu.Unlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not overwrite a newer reference mapping after the persistence scan.

findByRef runs outside s.mu, so a concurrent write can install a newer byRef[rk] while the scan is in progress. The unconditional assignment at Line 178 can then poison the cache with an older or deleted secret ID, causing later lookups to return stale data.

Install the result only if the key is still absent, or use a generation/compare-and-swap approach.

Proposed minimal fix
 s.mu.Lock()
-s.byRef[rk] = sec.ID
+if _, exists := s.byRef[rk]; !exists {
+	s.byRef[rk] = sec.ID
+}
 s.mu.Unlock()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sec, err := s.findByRef(ctx, workspace, ref)
if err != nil {
return nil, err
}
return s.GetByID(ctx, id)
s.mu.Lock()
s.byRef[rk] = sec.ID
s.mu.Unlock()
sec, err := s.findByRef(ctx, workspace, ref)
if err != nil {
return nil, err
}
s.mu.Lock()
if _, exists := s.byRef[rk]; !exists {
s.byRef[rk] = sec.ID
}
s.mu.Unlock()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/persis/store/secret.go` around lines 173 - 179, Update the cache
write in the method containing findByRef so it does not overwrite a mapping
created while the scan was in progress: under s.mu, check whether s.byRef[rk] is
still absent before assigning sec.ID. Preserve concurrent newer mappings and
keep the existing lookup error handling unchanged.

Copilot AI review requested due to automatic review settings July 30, 2026 03:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@yohamta0
yohamta0 merged commit f14dc93 into main Jul 30, 2026
11 checks passed
@yohamta0
yohamta0 deleted the agent/refresh-secret-ref-index branch July 30, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coordinator cannot resolve secrets created after startup

2 participants