<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Oauth on Lessons learned</title><link>https://eghuro.cz/tags/oauth/</link><description>Recent content in Oauth on Lessons learned</description><generator>Hugo</generator><language>en-us</language><copyright>© Alexandr Mansurov · Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)</copyright><lastBuildDate>Sat, 31 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://eghuro.cz/tags/oauth/index.xml" rel="self" type="application/rss+xml"/><item><title>Codex 401 in an agent shell: check credentials before blaming API key precedence</title><link>https://eghuro.cz/posts/codex-inherited-api-key/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid>https://eghuro.cz/posts/codex-inherited-api-key/</guid><description>&lt;p&gt;A regular terminal reported a ChatGPT login, but Codex requests inside an agent&amp;rsquo;s shell failed with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP error: 401 Unauthorized&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An inherited &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; looked suspicious. The useful next step was a controlled credential-context comparison. The recorded investigation never proved that the key overrode the ChatGPT login, and it never showed a successful retry after removing it.&lt;/p&gt;&#10;&#10;&lt;h2 id="what-the-may-5-investigation-established"&gt;What the May 5 investigation established &lt;/h2&gt;&#10;&lt;p&gt;The installed CLI was &lt;code&gt;codex-cli 0.128.0&lt;/code&gt;. The user reported that &lt;code&gt;codex login status&lt;/code&gt; returned &lt;code&gt;Logged in using ChatGPT&lt;/code&gt; before launching the agent. Inside the agent, the same status command returned &lt;code&gt;Not logged in&lt;/code&gt;, and this request repeatedly failed:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; --skip-git-repo-check &lt;span class="s2"&gt;&amp;#34;echo hello&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An early environment check found &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; present. A separate request to the OpenAI models endpoint, explicitly using that variable, returned HTTP 401. That established that the credential failed that direct probe. It did not establish which credential Codex selected.&lt;/p&gt;&#10;&lt;p&gt;Later checks found the variable absent, yet Codex still failed. One retry explicitly supplied an empty &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; and also received 401 responses. The chain contains no controlled &lt;code&gt;env -u&lt;/code&gt; comparison that restored subscription access. The document-cleanup task continued with Python; Codex authentication was not shown to recover.&lt;/p&gt;&#10;&lt;p&gt;Calling this a confirmed case of an environment key shadowing OAuth would overstate the evidence. The login-status mismatch deserved investigation before declaring the subscription token expired or replacing it with an API key.&lt;/p&gt;&#10;&#10;&lt;h2 id="compare-the-launch-contexts-without-exposing-secrets"&gt;Compare the launch contexts without exposing secrets &lt;/h2&gt;&#10;&lt;p&gt;Run these checks locally in both the working terminal and the failing agent shell:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;command&lt;/span&gt; -v codex&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;codex --version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;codex login status&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 - &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;PY&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;import os&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;for name in (&amp;#34;HOME&amp;#34;, &amp;#34;CODEX_HOME&amp;#34;, &amp;#34;OPENAI_API_KEY&amp;#34;, &amp;#34;CODEX_API_KEY&amp;#34;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;{name}: {&amp;#39;set&amp;#39; if name in os.environ else &amp;#39;unset&amp;#39;}&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;PY&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Keep login-status output private if it includes credential details. The Python check prints names and presence only. Do not dump the environment, search credential files, or paste tokens into a troubleshooting conversation.&lt;/p&gt;&#10;&lt;p&gt;Compare the actual &lt;code&gt;HOME&lt;/code&gt; and &lt;code&gt;CODEX_HOME&lt;/code&gt; paths privately, too. Presence alone cannot tell you whether two processes use the same directory. Check the executable and launch configuration for differences before changing authentication.&lt;/p&gt;&#10;&lt;p&gt;&lt;a&#10; href="https://developers.openai.com/codex/auth"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;OpenAI&amp;rsquo;s authentication documentation&lt;/a&gt;&#10; distinguishes ChatGPT subscription sign-in from API-key access. It also describes credential storage in a file under &lt;code&gt;CODEX_HOME&lt;/code&gt; or an OS credential store. A &lt;a&#10; href="https://eghuro.cz/posts/profile-home-tilde-paths"&#10; &#10; data-rel="prefetch"&#10; &#10; &#10;&gt;profile HOME mismatch&lt;/a&gt;&#10; or unavailable credential store is a separate explanation for a missing login.&lt;/p&gt;&#10;&lt;p&gt;To isolate the suspected variable on a Unix-like system, keep the same executable, working directory, &lt;code&gt;HOME&lt;/code&gt;, and &lt;code&gt;CODEX_HOME&lt;/code&gt;, then compare:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;codex login status&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;env -u OPENAI_API_KEY codex login status&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Removing one variable affects only the second process. It does not delete the saved login or change the parent shell. If you intend to make a model request, repeat the same minimal prompt with and without that variable and compare completion and exit status. A status check alone does not prove a request will succeed. This paired request test was not completed in the May investigation.&lt;/p&gt;&#10;&#10;&lt;h2 id="verification-on-october-31-avoid-a-universal-precedence-rule"&gt;Verification on October 31: avoid a universal precedence rule &lt;/h2&gt;&#10;&lt;p&gt;A separate check on 2026-10-31 used installed Codex &lt;code&gt;0.166.0&lt;/code&gt; with isolated, empty &lt;code&gt;HOME&lt;/code&gt; and &lt;code&gt;CODEX_HOME&lt;/code&gt; directories. &lt;code&gt;codex login status&lt;/code&gt; returned &lt;code&gt;Not logged in&lt;/code&gt; both without a key and with a dummy &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;. No model request or real credential was used. This checks status behavior without a saved login; it does not test precedence over OAuth.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a&#10; href="https://github.com/openai/codex/blob/67f0994b532b8740f14b5b738af7b7b2a915b1d6/codex-rs/login/src/auth/manager.rs#L1571-L1593"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;upstream authentication loader&lt;/a&gt;&#10;, inspected that day, has an environment-key precedence branch for &lt;code&gt;CODEX_API_KEY&lt;/code&gt;, conditional on that mechanism being enabled and API-key authentication being allowed. That is not a universal rule about &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;, nor proof of what version &lt;code&gt;0.128.0&lt;/code&gt; selected in May.&lt;/p&gt;&#10;&lt;p&gt;The earlier trusted-directory error was separate: &lt;code&gt;--skip-git-repo-check&lt;/code&gt; bypassed that check and exposed the authentication failure. Initializing unrelated directories as Git repositories would not repair credentials.&lt;/p&gt;&#10;&lt;p&gt;Before logging out or deleting a working login, compare the credential context of your two launch paths and change one variable at a time.&lt;/p&gt;&#10;</description></item><item><title>Google Drive drive.file + Picker: picking a folder grants the folder, not its contents</title><link>https://eghuro.cz/posts/google-drive-file-picker-folder-empty-list/</link><pubDate>Sun, 19 Apr 2026 00:17:00 +0200</pubDate><guid>https://eghuro.cz/posts/google-drive-file-picker-folder-empty-list/</guid><description>&lt;p&gt;A web app lets the user pick a Google Drive folder in the Google Picker, then lists that folder from the server with the Drive API v3. The folder was full of files. The listing came back like this, every time:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;files&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:[]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The status was 200, with no 403 and nothing in the response that mentioned permissions.&lt;/p&gt;&#10;&lt;p&gt;If you request the &lt;code&gt;drive.file&lt;/code&gt; scope and let users pick a folder, this is expected: the grant covers the folder itself and none of the files inside it.&lt;/p&gt;&#10;&#10;&lt;h2 id="setup"&gt;Setup &lt;/h2&gt;&#10;&lt;p&gt;The browser gets an OAuth access token with the &lt;code&gt;drive.file&lt;/code&gt; scope and opens the Picker on a folder view:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocsView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ViewId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FOLDERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setMimeTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/vnd.google-apps.folder&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setSelectFolderEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setIncludeFolders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;picker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PickerBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setOAuthToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setDeveloperKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onPicked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The callback sends the picked folder ID to the server, which walks the folder with &lt;code&gt;files.list&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://www.googleapis.com/drive/v3/files&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;folderId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&amp;#39; in parents and trashed = false`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fields&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nextPageToken, files(id, name, mimeType, size)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;supportsAllDrives&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;includeItemsFromAllDrives&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;drive.file&lt;/code&gt; looks like the obvious scope here. The &lt;a&#10; href="https://developers.google.com/workspace/drive/picker/guides/web-picker"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;Picker guide&lt;/a&gt;&#10; tells you to use it, and Google&amp;rsquo;s &lt;a&#10; href="https://developers.google.com/workspace/drive/api/guides/api-specific-auth"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;scope list&lt;/a&gt;&#10; marks it non-sensitive, so a public app needs only basic verification. The app only ever reads folders the user picked, which sounds like exactly what the scope is for.&lt;/p&gt;&#10;&#10;&lt;h2 id="the-dead-end-setappid"&gt;The dead end: setAppId &lt;/h2&gt;&#10;&lt;p&gt;The first suspect was &lt;code&gt;PickerBuilder.setAppId()&lt;/code&gt;. The setup above doesn&amp;rsquo;t call it, and the Picker guide says to &amp;ldquo;set the Drive App ID using the Cloud project number to allow the app to access the user&amp;rsquo;s files&amp;rdquo;. The theory was that without an app ID, the grant belongs to the access token that was live at pick time, so the server&amp;rsquo;s later call with a refreshed token sees nothing.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAppId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CLOUD_PROJECT_NUMBER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// the numeric project number, not the project ID&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you stay on &lt;code&gt;drive.file&lt;/code&gt;, set it, because the guide asks for it. It didn&amp;rsquo;t change the result here. The listing was still &lt;code&gt;{&amp;quot;files&amp;quot;:[]}&lt;/code&gt;.&lt;/p&gt;&#10;&#10;&lt;h2 id="whats-actually-going-on"&gt;What&amp;rsquo;s actually going on &lt;/h2&gt;&#10;&lt;p&gt;To see where the files went, the server logged the raw body of every &lt;code&gt;files.list&lt;/code&gt; response. A truly empty folder and a folder the app can&amp;rsquo;t see into give the same answer: status 200 and &lt;code&gt;&amp;quot;files&amp;quot;: []&lt;/code&gt;. The app only had access to the selected folder. Its children were never shared, so Drive left them out of the result and didn&amp;rsquo;t report an error.&lt;/p&gt;&#10;&lt;p&gt;The scope&amp;rsquo;s description says this once you read it with folders in mind. &lt;code&gt;drive.file&lt;/code&gt; covers files &amp;ldquo;that you open with an app or that the user shares with an app&amp;rdquo; — picking a folder shares one item: the folder. The docs never say the grant extends to its contents, and it doesn&amp;rsquo;t.&lt;/p&gt;&#10;&#10;&lt;h2 id="fix"&gt;Fix &lt;/h2&gt;&#10;&lt;p&gt;Pick the scope that matches what the server does. A server that reads everything under the picked folder can&amp;rsquo;t do that with &lt;code&gt;drive.file&lt;/code&gt;, so the app switched to &lt;code&gt;drive.readonly&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DRIVE_SCOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.googleapis.com/auth/drive.readonly&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It kept the Picker for choosing the folder and removed the &lt;code&gt;setAppId&lt;/code&gt; call, which &lt;code&gt;drive.readonly&lt;/code&gt; doesn&amp;rsquo;t need.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;drive.readonly&lt;/code&gt; is a &lt;a&#10; href="https://developers.google.com/workspace/drive/api/guides/api-specific-auth"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;restricted scope&lt;/a&gt;&#10;, so a public app needs Google&amp;rsquo;s restricted-scope verification to use it. The app in this story was internal to one Google Workspace domain, so verification didn&amp;rsquo;t apply.&lt;/p&gt;&#10;&lt;p&gt;Update the consent text in your own UI as well. &amp;ldquo;The app only sees folders you pick&amp;rdquo; was true under &lt;code&gt;drive.file&lt;/code&gt; and false after the switch. The new text says the app has read-only access to the whole Drive and downloads only from the folders the user picks.&lt;/p&gt;&#10;&lt;p&gt;Two other ways out may fit your constraints better. If users can pick files instead of a folder, enable multi-select in the Picker: every picked file is shared with the app, and &lt;code&gt;drive.file&lt;/code&gt; covers it. Or use a service account and have users share the folder with its address, which moves the access decision into Drive&amp;rsquo;s own sharing dialog. We ruled that out here for organizational reasons.&lt;/p&gt;&#10;&#10;&lt;h2 id="takeaway"&gt;Takeaway &lt;/h2&gt;&#10;&lt;p&gt;Under &lt;code&gt;drive.file&lt;/code&gt;, the Picker shares exactly the items the user selects. A folder is one item, and its children aren&amp;rsquo;t part of the grant, so listing it returns an empty &lt;code&gt;files&lt;/code&gt; array with status 200.&lt;/p&gt;&#10;&lt;p&gt;If your app requests &lt;code&gt;drive.file&lt;/code&gt; and calls &lt;code&gt;setSelectFolderEnabled(true)&lt;/code&gt;, list a folder you know isn&amp;rsquo;t empty from the server and log the raw response before you ship.&lt;/p&gt;&#10;</description></item></channel></rss>