{"record":{"id":"b6d11bb981deb678","repo":"multica-ai/multica","slug":"repo-is-configured-but-not-synced","errorCode":null,"errorMessage":"repo is configured but not synced","messagePattern":"repo is configured but not synced","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/daemon/daemon.go","lineNumber":3394,"sourceCode":"\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}\n\n\tif syncErr := d.workspaceLastRepoSyncErr(workspaceID); syncErr != \"\" {\n\t\treturn fmt.Errorf(\"repo is configured but not synced: %s\", syncErr)\n\t}\n\n\treturn fmt.Errorf(\"repo is configured but not synced\")\n}\n\n// DefaultTokenRenewalInterval is how often the daemon asks the server to\n// extend its PAT. The server-side threshold is 7 days of remaining lifetime;\n// polling every ~3 days gives at least two chances to renew before the\n// window closes, so a single failed call (network blip, server restart) does\n// not push the token out of the renewal window.\nconst DefaultTokenRenewalInterval = 3 * 24 * time.Hour\n\n// preflightAuth runs the two auth-sensitive startup steps in their\n// required order: a synchronous PAT renewal first, then the initial\n// workspace sync. The order matters — running tryRenewToken before any\n// other API call is what surfaces a user-actionable \"run multica login\"\n// WARN when the PAT is already revoked or expired. If we let the\n// workspace sync go first, its 401 would short-circuit Run before the\n// renewal loop's first tick ever fires, and the operator would see only\n// a generic auth failure in the workspace-sync log with no hint that\n// re-login is the fix.","sourceCodeStart":3376,"sourceCodeEnd":3412,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/daemon/daemon.go#L3376-L3412","documentation":"Same repo-access check as 878 but with no recorded sync error: the repo list refreshed fine, the workspace reports successful sync, yet the requested repoURL is absent from the synced repos. Typically a mismatch between the configured URL and the synced URL (or the server hasn't actually configured that repo for this workspace despite client-side caching).","triggerScenarios":"A task references repoURL 'git@github.com:org/repo.git' while the workspace synced 'https://github.com/org/repo' — same repo, different URL form — or the repo was recently removed from the workspace config server-side and a stale client still assumes access.","commonSituations":"SSH vs https URL forms for the same repo, trailing '.git' differences, repo removed from workspace configuration after tasks were queued, or newly added repos whose first sync hasn't completed.","solutions":["Compare the task's repoURL string with the workspace's configured repo URLs exactly (scheme, host, path, .git suffix)","Normalize both sides to one URL form (e.g. always https://host/org/repo.git) before submitting tasks","Re-run the workspace sync and retry once a newly added repo has finished its first clone","If the repo was intentionally removed, update the task/client to stop referencing it"],"exampleFix":"// before\nif err := task.RepoURL == wantURL { ... } // exact string compare, SSH vs https mismatch\n\n// after: canonicalize both URLs before comparing\nfunc canonicalRepoURL(raw string) string {\n    u := strings.TrimSuffix(raw, \".git\")\n    u = strings.Replace(u, \"git@github.com:\", \"github.com/\", 1)\n    u = strings.TrimPrefix(u, \"ssh://\")\n    return strings.TrimPrefix(u, \"https://\")\n}\nif canonicalRepoURL(task.RepoURL) != canonicalRepoURL(wantURL) { ... }","handlingStrategy":"validation","validationCode":"// Canonicalize repo URLs before comparing/submission.\nfunc canonicalRepoURL(raw string) string {\n    u := strings.TrimSuffix(strings.TrimSpace(raw), \".git\")\n    u = strings.Replace(u, \"git@github.com:\", \"github.com/\", 1)\n    u = strings.TrimPrefix(u, \"ssh://\")\n    u = strings.TrimPrefix(u, \"https://\")\n    u = strings.TrimPrefix(u, \"http://\")\n    return strings.ToLower(u)\n}\n\n// reject task dispatch when canonical forms differ\nif canonicalRepoURL(taskRepo) != canonicalRepoURL(configuredRepo) {\n    return fmt.Errorf(\"repo %q not in workspace config (did you mean %q?)\", taskRepo, configuredRepo)\n}","typeGuard":null,"tryCatchPattern":"On this error, do not retry: fetch the workspace's configured repo list, show it next to the requested URL, and fail the task with a 'URL form mismatch or repo not configured' message.","preventionTips":["Canonicalize repo URLs (scheme, .git suffix) at both task-submission and workspace-config time","Re-sync a workspace after adding a repo and wait for the first clone before dispatching tasks","Clean up tasks referencing repos that were removed from the workspace"],"tags":["daemon","repos","url-mismatch","workspace-config"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}