Skip to content

Commit 5f75a37

Browse files
friunscursoragent
andcommitted
Fix first-run install failure and add default workspace
- Fix installCodex() always returning false because isServerBundleInstalled() is hardcoded to false; now only checks isCodexInstalled() - Remove codex-web-local npm install from installCodex() (handled by bundled APK assets via installServerBundle()) - Add ensureCodexWrapperScript() to create/verify the codex shell wrapper independently of installNode() - Create default ~/codex workspace directory on setup so the folder dropdown is never empty on first launch - Add default "codex" folder option in Vue frontend when no threads exist - Resolve codex binary path using PREFIX env var in codexAppServerBridge to fix ENOENT spawn errors on Android Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 34a75f1 commit 5f75a37

4 files changed

Lines changed: 62 additions & 23 deletions

File tree

android/app/src/main/java/com/codex/mobile/CodexServerManager.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,30 @@ WEOF
192192
return false
193193
}
194194

195-
onProgress("Installing codex-web-local…")
196-
val webCode = runInPrefix(
197-
"node $npmCli install -g codex-web-local 2>&1",
198-
onOutput = { onProgress(it) },
199-
)
200-
if (webCode != 0) {
201-
Log.e(TAG, "npm install codex-web-local failed with code $webCode")
202-
return false
203-
}
195+
ensureCodexWrapperScript()
196+
return isCodexInstalled()
197+
}
198+
199+
fun ensureCodexWrapperScript() {
200+
val paths = BootstrapInstaller.getPaths(context)
201+
val prefix = paths.prefixDir
202+
val codexJs = File(prefix, "lib/node_modules/@openai/codex/bin/codex.js")
203+
val codexBin = File(prefix, "bin/codex")
204+
205+
if (!codexJs.exists()) return
206+
if (codexBin.exists()) return
204207

205-
return isCodexInstalled() && isServerBundleInstalled()
208+
val wrapperCmd = """
209+
rm -f "$prefix/bin/codex"
210+
cat > "$prefix/bin/codex" << 'WEOF'
211+
#!/data/user/0/com.codex.mobile/files/usr/bin/sh
212+
exec /data/user/0/com.codex.mobile/files/usr/bin/node /data/user/0/com.codex.mobile/files/usr/lib/node_modules/@openai/codex/bin/codex.js "${'$'}@"
213+
WEOF
214+
chmod 700 "$prefix/bin/codex"
215+
echo "codex wrapper created"
216+
""".trimIndent()
217+
runInPrefix(wrapperCmd)
218+
Log.i(TAG, "Created codex wrapper at $codexBin")
206219
}
207220

208221
fun installServerBundle(onProgress: (String) -> Unit): Boolean {
@@ -606,6 +619,16 @@ WEOF
606619
}
607620
}
608621

622+
fun ensureDefaultWorkspace() {
623+
val paths = BootstrapInstaller.getPaths(context)
624+
val workspaceDir = File(paths.homeDir, "codex")
625+
if (workspaceDir.exists()) return
626+
627+
workspaceDir.mkdirs()
628+
runInPrefix("cd ${workspaceDir.absolutePath} && git init 2>&1")
629+
Log.i(TAG, "Created default workspace at $workspaceDir")
630+
}
631+
609632
fun ensureFullAccessConfig() {
610633
val paths = BootstrapInstaller.getPaths(context)
611634
val configDir = File(paths.homeDir, ".codex")

android/app/src/main/java/com/codex/mobile/MainActivity.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,18 +149,22 @@ class MainActivity : AppCompatActivity() {
149149
}
150150
updateStatus("Node.js ready")
151151

152-
// Step 3: Install Codex CLI + web UI
153-
if (!serverManager.isCodexInstalled() || !serverManager.isServerBundleInstalled()) {
154-
updateStatus("Installing Codex…", "This may take a few minutes")
155-
156-
if (!serverManager.installServerBundle { msg -> updateDetail(msg) }) {
157-
val codexOk = serverManager.installCodex { msg -> updateDetail(msg) }
158-
if (!codexOk) {
159-
throw RuntimeException("Failed to install Codex")
160-
}
152+
// Step 3: Install Codex CLI
153+
if (!serverManager.isCodexInstalled()) {
154+
updateStatus("Installing Codex CLI…", "This may take a few minutes")
155+
val codexOk = serverManager.installCodex { msg -> updateDetail(msg) }
156+
if (!codexOk) {
157+
throw RuntimeException("Failed to install Codex")
161158
}
162159
}
163160

161+
// Ensure codex wrapper script exists
162+
serverManager.ensureCodexWrapperScript()
163+
164+
// Step 3a: Extract web UI from APK assets (every launch)
165+
updateStatus("Updating web UI…")
166+
serverManager.installServerBundle { msg -> updateDetail(msg) }
167+
164168
// Step 3b: Install native platform binary
165169
if (!serverManager.isPlatformBinaryInstalled()) {
166170
updateStatus("Installing Codex platform binary…")
@@ -171,8 +175,9 @@ class MainActivity : AppCompatActivity() {
171175
}
172176
updateStatus("Codex ready")
173177

174-
// Step 3c: Write full-access config (no approval prompts)
178+
// Step 3c: Write full-access config and create default workspace
175179
serverManager.ensureFullAccessConfig()
180+
serverManager.ensureDefaultWorkspace()
176181

177182
// Step 4: Start CONNECT proxy (needed for native binary DNS/TLS)
178183
updateStatus("Starting network proxy…")

src/App.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ const filteredMessages = computed(() =>
215215
const liveOverlay = computed(() => selectedLiveOverlay.value)
216216
const composerThreadContextId = computed(() => (isHomeRoute.value ? '__new-thread__' : selectedThreadId.value))
217217
const isSelectedThreadInProgress = computed(() => !isHomeRoute.value && selectedThread.value?.inProgress === true)
218+
const DEFAULT_WORKSPACE_NAME = 'codex'
219+
218220
const newThreadFolderOptions = computed(() => {
219221
const options: Array<{ value: string; label: string }> = []
220222
const seenCwds = new Set<string>()
@@ -229,6 +231,10 @@ const newThreadFolderOptions = computed(() => {
229231
})
230232
}
231233
234+
if (options.length === 0) {
235+
options.push({ value: DEFAULT_WORKSPACE_NAME, label: DEFAULT_WORKSPACE_NAME })
236+
}
237+
232238
return options
233239
})
234240

src/server/codexAppServerBridge.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
22
import { mkdtemp, readFile } from 'node:fs/promises'
33
import type { IncomingMessage, ServerResponse } from 'node:http'
44
import { tmpdir } from 'node:os'
5-
import { join } from 'node:path'
5+
import { join, dirname } from 'node:path'
6+
7+
const prefixBin = process.env.PREFIX ? join(process.env.PREFIX, 'bin') : ''
8+
const shellPath = prefixBin ? join(prefixBin, 'sh') : '/bin/sh'
69

710
type JsonRpcCall = {
811
jsonrpc: '2.0'
@@ -102,7 +105,8 @@ class AppServerProcess {
102105
if (this.process) return
103106

104107
this.stopping = false
105-
const proc = spawn('codex', ['app-server'], { stdio: ['pipe', 'pipe', 'pipe'] })
108+
const codexBin = prefixBin ? join(prefixBin, 'codex') : 'codex'
109+
const proc = spawn(codexBin, ['app-server'], { stdio: ['pipe', 'pipe', 'pipe'] })
106110
this.process = proc
107111

108112
proc.stdout.setEncoding('utf8')
@@ -372,7 +376,8 @@ class MethodCatalog {
372376

373377
private async runGenerateSchemaCommand(outDir: string): Promise<void> {
374378
await new Promise<void>((resolve, reject) => {
375-
const process = spawn('codex', ['app-server', 'generate-json-schema', '--out', outDir], {
379+
const codexBin = prefixBin ? join(prefixBin, 'codex') : 'codex'
380+
const process = spawn(codexBin, ['app-server', 'generate-json-schema', '--out', outDir], {
376381
stdio: ['ignore', 'ignore', 'pipe'],
377382
})
378383

0 commit comments

Comments
 (0)