{"record":{"id":"60b92834e491f5e3","repo":"multica-ai/multica","slug":"refresh-workspace-repos-w","errorCode":null,"errorMessage":"refresh workspace repos: %w","messagePattern":"refresh workspace repos: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/daemon/daemon.go","lineNumber":3370,"sourceCode":"\t//\n\t//   - cacheHitOnEntry=false but cache hit *after* we acquire the mutex:\n\t//     a sibling goroutine on a concurrent cold-miss already refreshed\n\t//     and populated the cache. We can skip the duplicate refresh — the\n\t//     sibling's refresh is fresh enough for our gate read.\n\tcacheHitOnEntry := d.workspaceRepoAllowed(workspaceID, repoURL) && d.repoCache.Lookup(workspaceID, repoURL) != \"\"\n\n\tif err := ws.repoRefreshMu.Lock(ctx); err != nil {\n\t\treturn err\n\t}\n\tdefer ws.repoRefreshMu.Unlock()\n\n\tif !cacheHitOnEntry && d.workspaceRepoAllowed(workspaceID, repoURL) && d.repoCache.Lookup(workspaceID, repoURL) != \"\" {\n\t\treturn nil\n\t}\n\n\tresp, err := d.refreshWorkspaceRepos(ctx, workspaceID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"refresh workspace repos: %w\", err)\n\t}\n\n\tif !d.workspaceRepoAllowed(workspaceID, repoURL) {\n\t\treturn ErrRepoNotConfigured\n\t}\n\n\tif d.repoCache.Lookup(workspaceID, repoURL) != \"\" {\n\t\treturn nil\n\t}\n\n\td.syncWorkspaceReposContext(ctx, workspaceID, resp.Repos)\n\tif err := ctx.Err(); err != nil {\n\t\treturn context.Cause(ctx)\n\t}\n\n\tif d.repoCache.Lookup(workspaceID, repoURL) != \"\" {\n\t\treturn nil\n\t}","sourceCodeStart":3352,"sourceCodeEnd":3388,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/daemon.go#L3352-L3388","documentation":"The daemon's repo-access check wraps a failed call to refreshWorkspaceRepos — the server API call that lists a workspace's configured repositories failed (network error, 5xx, or auth expiry). Downstream logic needs that list to decide whether a requested repoURL is allowed, so the transport failure is surfaced rather than treated as 'not configured'.","triggerScenarios":"Any task/clone path that goes through repo-permission checking while the server is unreachable, returns 500/503, or the daemon's token expired — i.e. refreshWorkspaceRepos returns err and the repo cache has no hit for the workspace.","commonSituations":"Server outages or deploys, daemon hibernating through laptop sleep with stale connections, expired PAT after the renewal window was missed, and local firewall/DNS failures.","solutions":["Check server reachability (curl the /api health endpoint) and the daemon's connectivity, then retry — this is usually transient","If auth-related, re-run 'multica login' and restart the daemon so the token refreshes","Look at the wrapped error's cause: a 5xx means server-side; a dial error means network/DNS","If it persists only for one workspace, check that workspace's repo configuration server-side"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Cheap reachability probe before the repo-permission path.\nif err := pingServer(ctx, c.client, 2*time.Second); err != nil {\n    return fmt.Errorf(\"server unreachable, skipping repo check: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"Retry with backoff around the wrapped call: unwrap the cause, retry only transient classes (net errors, 502/503/504, context deadline), and fail permanently on auth errors with a login hint.","preventionTips":["Keep the daemon's PAT renewal healthy (DefaultTokenRenewalInterval) so tokens never expire","Monitor server reachability from the daemon host","Cache the last good repo list to ride out brief outages"],"tags":["daemon","repos","server-api","network","transient"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}