mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +00:00
fix(github): repo cache eviction clears every host's entries, keeping a live twin's (#31312)
* fix(github): repo cache eviction drops every host scope's entries, keeping a twin's own Eviction and issue-source changes matched only unscoped key prefixes, so SSH-, runtime- and source-scoped entries survived repo removal. Keys are now composed by one builder and read back by its inverse, so eviction classifies each key by host and keeps only a surviving same-id twin's bucket. Hosted review (GitLab MR) entries are evicted too. Co-Authored-By: Claude <noreply@anthropic.com> * fix(github): read task-source scopes with empty parts back whole Co-Authored-By: Claude <noreply@anthropic.com> * fix(github): an issue-source change evicts only the changed twin's buckets Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,10 @@ import type { GlobalSettings } from '../global-settings-types'
|
||||
import {
|
||||
LOCAL_EXECUTION_HOST_ID,
|
||||
normalizeExecutionHostId,
|
||||
toSshExecutionHostId
|
||||
toSshExecutionHostId,
|
||||
type ExecutionHostId
|
||||
} from '../execution-host'
|
||||
import { taskSourceCacheScopeHostId } from '../task-source-context'
|
||||
|
||||
type RuntimeFocusSettings = Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> | null | undefined
|
||||
|
||||
@@ -16,14 +18,47 @@ export function getGitHubRepoCacheKey(
|
||||
executionHostId?: string | null,
|
||||
hasRepoOwner = false
|
||||
): string {
|
||||
const owner = repoId ?? repoPath
|
||||
const scope = getGitHubCacheHostScope(settings, connectionId, executionHostId, hasRepoOwner)
|
||||
// Why: runtime/SSH lookups can observe different remotes than the local repo
|
||||
// path, so cache keys include the repo's owning execution boundary.
|
||||
if (scope) {
|
||||
return `${scope}::${owner}::${suffix}`
|
||||
return composeRepoCacheKey(
|
||||
getGitHubCacheHostScope(settings, connectionId, executionHostId, hasRepoOwner),
|
||||
repoId ?? repoPath,
|
||||
suffix
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The one shape of a repo-owned cache key: `[scope::]owner::suffix`, where scope is an execution
|
||||
* host id or a task-source scope and no scope means this computer. Read back by repoCacheKeyHostId.
|
||||
*/
|
||||
export function composeRepoCacheKey(
|
||||
scope: string | null | undefined,
|
||||
owner: string,
|
||||
suffix: string
|
||||
): string {
|
||||
return scope ? `${scope}::${owner}::${suffix}` : `${owner}::${suffix}`
|
||||
}
|
||||
|
||||
/** Which host's bucket `key` is when one of `owners` (repo id or path) owns it; null when none does. */
|
||||
export function repoCacheKeyHostId(key: string, owners: readonly string[]): ExecutionHostId | null {
|
||||
if (owners.some((owner) => key.startsWith(`${owner}::`))) {
|
||||
return LOCAL_EXECUTION_HOST_ID
|
||||
}
|
||||
return `${owner}::${suffix}`
|
||||
// Why not the first `::`: a task-source scope with empty parts contains `::` itself.
|
||||
for (const owner of owners) {
|
||||
const marker = `::${owner}::`
|
||||
for (let at = key.indexOf(marker); at !== -1; at = key.indexOf(marker, at + 1)) {
|
||||
const scope = key.slice(0, at)
|
||||
// Why the `::` check: a composed host id is encoded, so a candidate spanning `::` is owner + suffix.
|
||||
const hostId =
|
||||
(scope.includes('::') ? null : normalizeExecutionHostId(scope)) ??
|
||||
taskSourceCacheScopeHostId(scope)
|
||||
if (hostId) {
|
||||
return hostId
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getGitHubCacheHostScope(
|
||||
@@ -57,7 +92,7 @@ export function getLegacyGitHubRepoCacheKey(
|
||||
repoId: string | undefined,
|
||||
suffix: string
|
||||
): string {
|
||||
return `${repoId ?? repoPath}::${suffix}`
|
||||
return composeRepoCacheKey(null, repoId ?? repoPath, suffix)
|
||||
}
|
||||
|
||||
export function getGitHubPRCacheKey(
|
||||
|
||||
Reference in New Issue
Block a user