Run automations after a repo's project identity changes (#7462)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-07-05 20:56:26 -07:00
committed by GitHub
co-authored by Orca
parent 61bd98db6f
commit b2d079c80d
4 changed files with 222 additions and 6 deletions
@@ -140,6 +140,72 @@ describe('automation target availability', () => {
).toBe('host-mismatch')
})
it('allows a run context whose derived projectId tier drifted but repo/host/path still match', () => {
expect(
getAutomationTargetAvailability({
automation: makeAutomation({
runContext: {
kind: 'workspace-run',
// Snapshotted at the repo: tier before the setup climbed to github:.
projectId: 'repo:repo-1',
hostId: 'local',
projectHostSetupId: 'setup-1',
repoId: 'repo-1',
path: '/repo'
}
}),
repo: makeRepo(),
workspace: makeWorkspace(),
projectHostSetups: [makeProjectHostSetup({ projectId: 'github:o/r' })],
sshConnectionStates: new Map()
})
).toEqual({ canRunNow: true, reason: 'available', message: null })
})
it('still blocks a run context whose repoId no longer matches despite projectId drift', () => {
expect(
getAutomationTargetAvailability({
automation: makeAutomation({
runContext: {
kind: 'workspace-run',
projectId: 'repo:repo-2',
hostId: 'local',
projectHostSetupId: 'setup-1',
repoId: 'repo-2',
path: '/repo'
}
}),
repo: makeRepo(),
workspace: makeWorkspace(),
projectHostSetups: [makeProjectHostSetup({ projectId: 'github:o/r', repoId: 'repo-2' })],
sshConnectionStates: new Map()
}).reason
).toBe('host-mismatch')
})
it('still blocks when the setup repoId drifts even though the live repo still matches', () => {
// Live repo matches runContext.repoId (repoMatchesContext passes), so this
// isolates the setup-side repoId clause that survives the projectId removal.
expect(
getAutomationTargetAvailability({
automation: makeAutomation({
runContext: {
kind: 'workspace-run',
projectId: 'repo:repo-1',
hostId: 'local',
projectHostSetupId: 'setup-1',
repoId: 'repo-1',
path: '/repo'
}
}),
repo: makeRepo(),
workspace: makeWorkspace(),
projectHostSetups: [makeProjectHostSetup({ projectId: 'github:o/r', repoId: 'repo-2' })],
sshConnectionStates: new Map()
}).reason
).toBe('host-mismatch')
})
it('allows remote-listed SSH automations whose repo is projected through a runtime server', () => {
expect(
getAutomationTargetAvailability({
@@ -96,8 +96,10 @@ export function getAutomationTargetAvailability({
`Project setup on the selected automation host is ${setup.setupState}.`
)
}
// Why: projectId is a derived identity that upgrades over time (repo:→git:→github:);
// matching on it strands automations created before their repo's identity resolved.
// Anchor on repoId/path/host instead — the durable, stable target identity.
const setupMatchesContext =
setup.projectId === automation.runContext.projectId &&
setup.repoId === automation.runContext.repoId &&
setup.path === automation.runContext.path &&
setupHostMatchesRunContext(setup.hostId, automation.runContext.hostId, automationHostTarget)