@@ -1824,6 +1824,122 @@ describe("storage", () => {
18241824 } ;
18251825 expect ( seeded . accounts ?. [ 0 ] ?. accountId ) . toBe ( "global-account" ) ;
18261826 } ) ;
1827+
1828+ it ( "seeds project storage only once for concurrent global-fallback loads" , async ( ) => {
1829+ const fakeHome = join ( testWorkDir , "home-fallback-concurrent" ) ;
1830+ const projectDir = join ( testWorkDir , "project-fallback-concurrent" ) ;
1831+ const projectGitDir = join ( projectDir , ".git" ) ;
1832+ const globalConfigDir = join ( fakeHome , ".opencode" ) ;
1833+ const globalStoragePath = join ( globalConfigDir , "openai-codex-accounts.json" ) ;
1834+
1835+ await fs . mkdir ( fakeHome , { recursive : true } ) ;
1836+ await fs . mkdir ( projectGitDir , { recursive : true } ) ;
1837+ await fs . mkdir ( globalConfigDir , { recursive : true } ) ;
1838+ process . env . HOME = fakeHome ;
1839+ process . env . USERPROFILE = fakeHome ;
1840+ setStoragePath ( projectDir ) ;
1841+
1842+ const globalStorage = {
1843+ version : 3 ,
1844+ activeIndex : 0 ,
1845+ accounts : [
1846+ {
1847+ refreshToken : "global-refresh-concurrent" ,
1848+ accountId : "global-account-concurrent" ,
1849+ addedAt : 1 ,
1850+ lastUsed : 1 ,
1851+ } ,
1852+ ] ,
1853+ } ;
1854+ await fs . writeFile ( globalStoragePath , JSON . stringify ( globalStorage ) , "utf-8" ) ;
1855+
1856+ const projectScopedPath = getStoragePath ( ) ;
1857+ const originalRename = fs . rename . bind ( fs ) ;
1858+ let projectSeedWriteCount = 0 ;
1859+ const renameSpy = vi . spyOn ( fs , "rename" ) . mockImplementation ( async ( sourcePath , destinationPath ) => {
1860+ if ( String ( destinationPath ) === projectScopedPath ) {
1861+ projectSeedWriteCount += 1 ;
1862+ }
1863+ return originalRename ( sourcePath , destinationPath ) ;
1864+ } ) ;
1865+
1866+ try {
1867+ const [ first , second ] = await Promise . all ( [ loadAccounts ( ) , loadAccounts ( ) ] ) ;
1868+ expect ( first ?. accounts [ 0 ] ?. accountId ) . toBe ( "global-account-concurrent" ) ;
1869+ expect ( second ?. accounts [ 0 ] ?. accountId ) . toBe ( "global-account-concurrent" ) ;
1870+ expect ( projectSeedWriteCount ) . toBe ( 1 ) ;
1871+ } finally {
1872+ renameSpy . mockRestore ( ) ;
1873+ }
1874+ } ) ;
1875+
1876+ it ( "returns global fallback when project seed write fails" , async ( ) => {
1877+ const fakeHome = join ( testWorkDir , "home-fallback-seed-fail" ) ;
1878+ const projectDir = join ( testWorkDir , "project-fallback-seed-fail" ) ;
1879+ const projectGitDir = join ( projectDir , ".git" ) ;
1880+ const globalConfigDir = join ( fakeHome , ".opencode" ) ;
1881+ const globalStoragePath = join ( globalConfigDir , "openai-codex-accounts.json" ) ;
1882+
1883+ await fs . mkdir ( fakeHome , { recursive : true } ) ;
1884+ await fs . mkdir ( projectGitDir , { recursive : true } ) ;
1885+ await fs . mkdir ( globalConfigDir , { recursive : true } ) ;
1886+ process . env . HOME = fakeHome ;
1887+ process . env . USERPROFILE = fakeHome ;
1888+ setStoragePath ( projectDir ) ;
1889+
1890+ const globalStorage = {
1891+ version : 3 ,
1892+ activeIndex : 0 ,
1893+ accounts : [
1894+ {
1895+ refreshToken : "global-refresh-fail" ,
1896+ accountId : "global-account-fail" ,
1897+ addedAt : 1 ,
1898+ lastUsed : 1 ,
1899+ } ,
1900+ ] ,
1901+ } ;
1902+ await fs . writeFile ( globalStoragePath , JSON . stringify ( globalStorage ) , "utf-8" ) ;
1903+
1904+ const projectScopedPath = getStoragePath ( ) ;
1905+ const originalRename = fs . rename . bind ( fs ) ;
1906+ const renameSpy = vi . spyOn ( fs , "rename" ) . mockImplementation ( async ( sourcePath , destinationPath ) => {
1907+ if ( String ( destinationPath ) === projectScopedPath ) {
1908+ const err = new Error ( "EPERM seed failure" ) as NodeJS . ErrnoException ;
1909+ err . code = "EPERM" ;
1910+ throw err ;
1911+ }
1912+ return originalRename ( sourcePath , destinationPath ) ;
1913+ } ) ;
1914+
1915+ try {
1916+ const loaded = await loadAccounts ( ) ;
1917+ expect ( loaded ?. accounts [ 0 ] ?. accountId ) . toBe ( "global-account-fail" ) ;
1918+ expect ( existsSync ( projectScopedPath ) ) . toBe ( false ) ;
1919+ } finally {
1920+ renameSpy . mockRestore ( ) ;
1921+ }
1922+ } ) ;
1923+
1924+ it ( "returns null when global fallback storage is corrupted" , async ( ) => {
1925+ const fakeHome = join ( testWorkDir , "home-fallback-corrupted" ) ;
1926+ const projectDir = join ( testWorkDir , "project-fallback-corrupted" ) ;
1927+ const projectGitDir = join ( projectDir , ".git" ) ;
1928+ const globalConfigDir = join ( fakeHome , ".opencode" ) ;
1929+ const globalStoragePath = join ( globalConfigDir , "openai-codex-accounts.json" ) ;
1930+
1931+ await fs . mkdir ( fakeHome , { recursive : true } ) ;
1932+ await fs . mkdir ( projectGitDir , { recursive : true } ) ;
1933+ await fs . mkdir ( globalConfigDir , { recursive : true } ) ;
1934+ process . env . HOME = fakeHome ;
1935+ process . env . USERPROFILE = fakeHome ;
1936+ setStoragePath ( projectDir ) ;
1937+
1938+ await fs . writeFile ( globalStoragePath , "{ invalid json" , "utf-8" ) ;
1939+
1940+ await expect ( loadAccounts ( ) ) . resolves . toBeNull ( ) ;
1941+ expect ( existsSync ( getStoragePath ( ) ) ) . toBe ( false ) ;
1942+ } ) ;
18271943 } ) ;
18281944
18291945 describe ( "saveAccounts EPERM/EBUSY retry logic" , ( ) => {
0 commit comments