Add background mode to run_shell_command (fix Termux timeout on servers) - #7
Conversation
Long-running commands (servers, watchers, daemons) blocked the Termux bridge's 12 s reply window, surfacing as "Termux did not reply" even when the process was in fact running. Add a `background: true` flag that wraps the command with nohup + output redirection to ~/.gemini-bg/run-<id>.log, disowns it, and returns within ~200 ms with the PID and log path once the process is confirmed alive. Gemini can then poll progress via `tail -n 80 <log>` and stop the process with `kill <pid>` — both regular foreground shell calls.
Reviewer's GuideAdds a background execution mode to Termux shell commands so long-running servers can be started without hitting the 12s timeout, including safe bash wrapping and updated tool metadata for the Gemini shell tool. Sequence diagram for background Termux shell command executionsequenceDiagram
actor GeminiModel
participant RunShellCommandTool
participant TermuxBridge
participant TermuxRunCommandService as Termux_RunCommandService
participant Bash
participant BackgroundProcess
GeminiModel->>RunShellCommandTool: run_shell_command(command, background=true)
RunShellCommandTool->>RunShellCommandTool: parse arguments
RunShellCommandTool->>TermuxBridge: run(command, workdir, timeoutMs=12000, background=true)
TermuxBridge->>TermuxBridge: wrapBackground(command)
TermuxBridge->>TermuxRunCommandService: dispatch(wrappedCommand, workdir, timeoutMs)
TermuxRunCommandService->>Bash: execute wrappedCommand
Bash->>Bash: mkdir -p ~/.gemini-bg
Bash->>Bash: LOG=~/.gemini-bg/run-<id>.log
Bash->>BackgroundProcess: nohup bash -lc <user command> &
Bash->>Bash: PID=$!
Bash->>Bash: disown
Bash->>Bash: sleep 0.2
Bash->>BackgroundProcess: kill -0 $PID
alt process alive
Bash-->>TermuxRunCommandService: echo [background] started pid=<pid> log=<log>
TermuxRunCommandService-->>TermuxBridge: exit=0, stdout
TermuxBridge-->>RunShellCommandTool: Result(exitCode=0, stdout)
RunShellCommandTool-->>GeminiModel: exit=0 mode=background cwd=...
else process crashed immediately
Bash-->>TermuxRunCommandService: echo error + tail last log lines
TermuxRunCommandService-->>TermuxBridge: exit=1, stdout
TermuxBridge-->>RunShellCommandTool: Result(exitCode=1, stdout)
RunShellCommandTool-->>GeminiModel: exit=1 mode=background cwd=...
end
BackgroundProcess-->>BackgroundProcess: keeps running in Termux
Class diagram for updated TermuxBridge and RunShellCommandToolclassDiagram
class TermuxBridge {
-Context appContext
-boolean autoSetupAttempted
+suspend run(command: String, workdir: String?, timeoutMs: Long, background: Boolean): Result
-suspend dispatch(command: String, workdir: String?, timeoutMs: Long): Result
-boolean isInstalled()
-boolean isWorkdirDenied(result: Result)
-void triggerStorageBootstrap()
-String wrapBackground(command: String)
-String bashSingleQuote(s: String)
}
class RunShellCommandTool {
-Workspace workspace
-TermuxBridge termux
+RunShellCommandTool(workspace: Workspace, termux: TermuxBridge)
+suspend execute(call: ToolCall): ToolCallResult
}
class ToolCall {
+Map~String, Any?~ arguments
}
class ToolCallResult
class Workspace {
+String absolutePath()
+String? unreachableReason()
}
class Result {
+boolean ok
+int exitCode
+String stdout
+String stderr
}
TermuxBridge --> Result
RunShellCommandTool --> TermuxBridge
RunShellCommandTool --> Workspace
RunShellCommandTool --> ToolCall
RunShellCommandTool --> ToolCallResult
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughThe changes introduce a Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt" line_range="221-223" />
<code_context>
+ val quoted = bashSingleQuote(command)
+ return buildString {
+ append("mkdir -p \"\$HOME/.gemini-bg\" && ")
+ append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ")
+ append("nohup bash -lc $quoted > \"\$LOG\" 2>&1 & ")
+ append("PID=\$! && ")
</code_context>
<issue_to_address>
**suggestion (performance):** Background log files under ~/.gemini-bg can grow without bound; consider a retention/cleanup strategy.
Each background run writes to a new file in `~/.gemini-bg/` with no rotation or pruning, so logs can grow without limit. Please consider adding a simple retention mechanism (e.g., keep only the last N logs, cap total directory size, or provide a cleanup command) to avoid long‑term disk usage issues.
```suggestion
return buildString {
append("mkdir -p \"\$HOME/.gemini-bg\" && ")
// simple retention: keep only the 20 most recent background logs
append("{ ls -1t \"\$HOME/.gemini-bg\"/run-*.log 2>/dev/null | tail -n +21 | xargs -r rm -- 2>/dev/null || true ; } && ")
append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ")
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return buildString { | ||
| append("mkdir -p \"\$HOME/.gemini-bg\" && ") | ||
| append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ") |
There was a problem hiding this comment.
suggestion (performance): Background log files under ~/.gemini-bg can grow without bound; consider a retention/cleanup strategy.
Each background run writes to a new file in ~/.gemini-bg/ with no rotation or pruning, so logs can grow without limit. Please consider adding a simple retention mechanism (e.g., keep only the last N logs, cap total directory size, or provide a cleanup command) to avoid long‑term disk usage issues.
| return buildString { | |
| append("mkdir -p \"\$HOME/.gemini-bg\" && ") | |
| append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ") | |
| return buildString { | |
| append("mkdir -p \"\$HOME/.gemini-bg\" && ") | |
| // simple retention: keep only the 20 most recent background logs | |
| append("{ ls -1t \"\$HOME/.gemini-bg\"/run-*.log 2>/dev/null | tail -n +21 | xargs -r rm -- 2>/dev/null || true ; } && ") | |
| append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ") |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt (1)
39-43: Consider documenting the failure mode and log-retrieval contract in thebackgroundparam description.The current description tells the model the happy path but not:
- that if the process dies within ~200 ms the call returns
exit=1with the tail of the log (so the model should read stdout on failure rather than blindly retrying), and- that the
log=<path>line in stdout is the canonical handle — stable wording helps the model parse it reliably for the follow-uptail/kill.Purely a description tweak, no behavior change.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt` around lines 39 - 43, Update the "background" booleanProp description in ShellTool (the "background" to booleanProp(...) entry) to document the failure mode and log-retrieval contract: state that if the backgrounded process dies within ~200 ms the call returns exit=1 and prints the tail of the log to stdout (so callers should read stdout on failure), and explicitly declare that a line of the form "log=<path>" in stdout is the canonical, stable handle to use for subsequent tail/kill operations. Keep the rest of the behavior text but append these two clarifying sentences.core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt (1)
218-236: Minor:~/.gemini-bg/log files are never rotated or cleaned up.Each background invocation creates a new
run-<id>.logand nothing ever prunes them. For a long-lived install this grows unbounded. Consider a best-effort cleanup of logs older than N days at the top ofwrapBackground, e.g.:+ append("find \"\$HOME/.gemini-bg\" -type f -name 'run-*.log' -mtime +7 -delete 2>/dev/null ; ") append("mkdir -p \"\$HOME/.gemini-bg\" || exit 1 ; ")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt` around lines 218 - 236, In wrapBackground, add a best-effort cleanup step before creating the new log so old run-<id>.log files under $HOME/.gemini-bg are pruned (e.g. use a find ... -mtime +N -type f -name "run-*.log" -delete or move to rotated names) and silence failures; update the buildString to prepend this command (or a configurable retention N) so LOG creation and nohup still proceed if cleanup fails, referencing wrapBackground and the LOG="\$HOME/.gemini-bg/run-$id.log" variable to locate where to run the prune.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt`:
- Around line 218-236: In TermuxBridge.wrapBackground, the current single-line
chain lets the entire chain be backgrounded so PID=$! captures the subshell, not
the actual nohup bash process; split the commands so you run mkdir and export
LOG, then run nohup bash -lc <quoted> > "$LOG" 2>&1 </dev/null & and immediately
capture PID=$! (then run disown 2>/dev/null || true and the kill-check logic
against that PID); also add the stdin redirection </dev/null to the nohup
invocation and ensure the subsequent tail/exit logic still references the same
LOG and PID.
In `@core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt`:
- Line 50: The current silent cast of call.arguments["background"] to Boolean in
ShellTool.kt drops non-Boolean JSON values (e.g., "true") and defaults to false;
change the extraction to coerce/validate multiple types instead: read the raw
value from call.arguments["background"], then handle Boolean (use as-is), String
(parse case-insensitive "true"/"false"), and Number (treat 1 as true, 0 as
false), and for any other or unparseable value either log a warning (include the
raw value) and default to a safe choice or throw a clear validation exception;
update the binding where background is defined (the val background assignment
inside ShellTool) and include a short log/validation message referencing the raw
argument so mis-typed inputs are visible at runtime.
---
Nitpick comments:
In `@core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt`:
- Around line 218-236: In wrapBackground, add a best-effort cleanup step before
creating the new log so old run-<id>.log files under $HOME/.gemini-bg are pruned
(e.g. use a find ... -mtime +N -type f -name "run-*.log" -delete or move to
rotated names) and silence failures; update the buildString to prepend this
command (or a configurable retention N) so LOG creation and nohup still proceed
if cleanup fails, referencing wrapBackground and the
LOG="\$HOME/.gemini-bg/run-$id.log" variable to locate where to run the prune.
In `@core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt`:
- Around line 39-43: Update the "background" booleanProp description in
ShellTool (the "background" to booleanProp(...) entry) to document the failure
mode and log-retrieval contract: state that if the backgrounded process dies
within ~200 ms the call returns exit=1 and prints the tail of the log to stdout
(so callers should read stdout on failure), and explicitly declare that a line
of the form "log=<path>" in stdout is the canonical, stable handle to use for
subsequent tail/kill operations. Keep the rest of the behavior text but append
these two clarifying sentences.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f7d15b03-2797-44f4-a400-62b21f20bb05
📒 Files selected for processing (2)
core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.ktcore-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt
| private fun wrapBackground(command: String): String { | ||
| val id = UUID.randomUUID().toString().take(8) | ||
| val quoted = bashSingleQuote(command) | ||
| return buildString { | ||
| append("mkdir -p \"\$HOME/.gemini-bg\" && ") | ||
| append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ") | ||
| append("nohup bash -lc $quoted > \"\$LOG\" 2>&1 & ") | ||
| append("PID=\$! && ") | ||
| append("disown 2>/dev/null || true ; ") | ||
| append("sleep 0.2 ; ") | ||
| append("if kill -0 \$PID 2>/dev/null ; then ") | ||
| append(" echo \"[background] started pid=\$PID log=\$LOG\" ; ") | ||
| append("else ") | ||
| append(" echo \"[background] process died immediately; last log lines:\" ; ") | ||
| append(" tail -n 30 \"\$LOG\" 2>/dev/null || true ; ") | ||
| append(" exit 1 ; ") | ||
| append("fi") | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm bash precedence: is `A && B & C` parsed as `(A && B) & ; C` ?
# Expected: $! is the PID of the (A && B) group, not of B alone.
bash -c '
true && sleep 3 & PID=$!
echo "captured PID=$PID"
ps -o pid,ppid,cmd -p $PID 2>/dev/null || echo "PID $PID not a direct sleep process"
# Show children — if PID is a subshell wrapping nohup, sleep will be its child
pgrep -P $PID | xargs -r ps -o pid,ppid,cmd -p
wait
'Repository: aciderix/gemini-android-app
Length of output: 174
🏁 Script executed:
#!/bin/bash
# Test 1: Verify bash operator precedence for `A && B & C`
# Does it parse as `(A && B) & ; C` or `(A && B & C)` ?
echo "=== Test 1: Operator Precedence ==="
bash -c '
# If parsed as (A && B) & ; C then PID=$! captures the group
# If parsed as (A && B & C) then the entire sequence is backgrounded
true && (echo "B executed") & PID=$!
echo "Captured PID=$PID"
# Wait a moment for background job to complete
sleep 0.5
wait $PID 2>/dev/null && echo "Successfully waited for $PID"
'
# Test 2: More explicit test with process tree
echo -e "\n=== Test 2: What is $! when using && and & ? ==="
bash -c '
# Create a marker file to track execution order
true && sleep 10 &
PID=$!
echo "Backgrounded PID=$PID"
# Check if PID still exists
if ps -p $PID > /dev/null 2>&1; then
echo "PID $PID still running"
ps -o pid,ppid,cmd -p $PID 2>/dev/null | head -5
fi
kill $PID 2>/dev/null
wait 2>/dev/null
'
# Test 3: Verify the exact TermuxBridge pattern
echo -e "\n=== Test 3: Simulating TermuxBridge nohup pattern ==="
tmpdir=$(mktemp -d)
bash -c "
mkdir -p \"$tmpdir\" && LOG=\"$tmpdir/test.log\" && nohup sleep 10 > \"\$LOG\" 2>&1 & PID=\$!
echo \"Captured PID=\$PID\"
# What process is $PID?
ps -o pid,ppid,cmd -p \$PID 2>/dev/null | head -2
# Cleanup
kill \$PID 2>/dev/null || true
wait 2>/dev/null || true
"
rm -rf "$tmpdir"Repository: aciderix/gemini-android-app
Length of output: 296
🏁 Script executed:
#!/bin/bash
# Check if the code in the repository matches the review snippet
cat -n core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt | sed -n '218,236p'Repository: aciderix/gemini-android-app
Length of output: 1095
🏁 Script executed:
#!/bin/bash
echo "=== Detailed Test: Process Tree Analysis ==="
tmpdir=$(mktemp -d)
# Test the actual pattern with detailed process inspection
bash -c "
# This simulates the exact TermuxBridge pattern
mkdir -p \"$tmpdir\" && LOG=\"$tmpdir/test.log\" && nohup bash -c 'sleep 30' > \"\$LOG\" 2>&1 & PID=\$!
echo \"Captured PID=\$PID\"
# Get details about the captured PID
echo \"Process info for captured PID \$PID:\"
ps -o pid,ppid,sid,cmd -p \$PID 2>/dev/null || echo \"PID \$PID not found\"
# Show all processes in this shell's process group
echo \"All processes in current shell environment:\"
ps -o pid,ppid,sid,cmd 2>/dev/null | grep -E '(bash|sleep|PID)' | head -20
# Wait briefly and check again
sleep 1
echo \"After 1 second - checking if sleep is still under PID \$PID:\"
pgrep -P \$PID 2>/dev/null || echo \"No children under PID \$PID\"
# Cleanup
kill -9 \$PID 2>/dev/null || true
wait 2>/dev/null || true
" 2>&1
# Also test: what is the difference between capturing on the same line vs different lines?
echo -e "\n=== Comparison: Same line vs different line ==="
bash -c "
mkdir -p \"$tmpdir\" && LOG=\"$tmpdir/test2.log\" && nohup bash -c 'sleep 30' > \"\$LOG\" 2>&1 &
BASHPID_LINE=\$BASHPID
PID_SAME=\$!
# Separate capture on next line
mkdir -p \"$tmpdir\" && LOG=\"$tmpdir/test3.log\" && nohup bash -c 'sleep 30' > \"\$LOG\" 2>&1 &
PID_NEXT=\$!
echo \"PID captured on same line (after &): \$PID_SAME\"
echo \"PID captured on next line: \$PID_NEXT\"
# Cleanup
kill -9 \$PID_SAME \$PID_NEXT 2>/dev/null || true
wait 2>/dev/null || true
"
rm -rf "$tmpdir"Repository: aciderix/gemini-android-app
Length of output: 423
🏁 Script executed:
#!/bin/bash
# Search for related code and comments
rg -B5 -A10 "wrapBackground" core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.ktRepository: aciderix/gemini-android-app
Length of output: 1888
PID=$! captures the backgrounded subshell, not the nohup bash process.
In bash, & has lower precedence than &&. The line:
mkdir -p "$HOME/.gemini-bg" && LOG="…" && nohup bash -lc '…' > "$LOG" 2>&1 & PID=$! && disown …
parses as (mkdir && LOG=… && nohup bash … > "$LOG" 2>&1) & ; PID=$! && disown … — the entire chain runs in one backgrounded subshell. $! captures that subshell's PID, not the inner nohup bash process. Consequences:
nohup bash -lc '…'runs inside the subshell (not backgrounded again), so the subshell waits for it to complete before exiting.$!is the temporary subshell's PID, which exits after launchingnohup bash. The actual daemon process becomes a child of init.- The PID printed to the model (
[background] started pid=$PID) is incorrect. When the model later runskill <pid>, it targets a defunct subshell while the actual server continues running. disown 2>/dev/nulltargets$!(the subshell), having no effect on thenohupchild.
Suggested fix: don't chain with && across the &, and capture $! immediately after the nohup line:
🛠️ Proposed fix
private fun wrapBackground(command: String): String {
val id = UUID.randomUUID().toString().take(8)
val quoted = bashSingleQuote(command)
return buildString {
- append("mkdir -p \"\$HOME/.gemini-bg\" && ")
- append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" && ")
- append("nohup bash -lc $quoted > \"\$LOG\" 2>&1 & ")
- append("PID=\$! && ")
- append("disown 2>/dev/null || true ; ")
- append("sleep 0.2 ; ")
+ append("mkdir -p \"\$HOME/.gemini-bg\" || exit 1 ; ")
+ append("LOG=\"\$HOME/.gemini-bg/run-$id.log\" ; ")
+ append("nohup bash -lc $quoted > \"\$LOG\" 2>&1 </dev/null & ")
+ append("PID=\$! ; ")
+ append("disown \$PID 2>/dev/null || true ; ")
+ append("sleep 0.2 ; ")
append("if kill -0 \$PID 2>/dev/null ; then ")
append(" echo \"[background] started pid=\$PID log=\$LOG\" ; ")
append("else ")
append(" echo \"[background] process died immediately; last log lines:\" ; ")
append(" tail -n 30 \"\$LOG\" 2>/dev/null || true ; ")
append(" exit 1 ; ")
append("fi")
}
}Also add </dev/null to prevent the backgrounded bash inheriting closed stdin from RunCommandService and receiving SIGTTIN on accidental reads.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core-bridge/src/main/kotlin/com/gemini/bridge/termux/TermuxBridge.kt` around
lines 218 - 236, In TermuxBridge.wrapBackground, the current single-line chain
lets the entire chain be backgrounded so PID=$! captures the subshell, not the
actual nohup bash process; split the commands so you run mkdir and export LOG,
then run nohup bash -lc <quoted> > "$LOG" 2>&1 </dev/null & and immediately
capture PID=$! (then run disown 2>/dev/null || true and the kill-check logic
against that PID); also add the stdin redirection </dev/null to the nohup
invocation and ensure the subsequent tail/exit logic still references the same
LOG and PID.
|
|
||
| override suspend fun execute(call: ToolCall): ToolCallResult = runCatching { | ||
| val command = call.arguments["command"] as? String ?: error("command is required") | ||
| val background = call.arguments["background"] as? Boolean ?: false |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find how tool arguments are deserialized — do they land as typed JSON (Boolean/Number/String)
# or everything as String? Look at the JSON parser feeding ToolCall.arguments.
rg -nP --type=kotlin -C4 '\bToolCall\s*\('
rg -nP --type=kotlin -C3 'arguments\s*[:=]\s*Map'
rg -nP --type=kotlin -C5 'fun\s+\w*[Pp]arse\w*Tool|fromJson|decodeFromString.*Tool'Repository: aciderix/gemini-android-app
Length of output: 4089
🏁 Script executed:
rg -nP --type=kotlin 'fun\s+jsonToMap|jsonToMap\s*\(' -A 10Repository: aciderix/gemini-android-app
Length of output: 5582
🏁 Script executed:
cat -n core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt | head -70 | tail -30Repository: aciderix/gemini-android-app
Length of output: 1952
🏁 Script executed:
rg -nP --type=kotlin 'fun\s+booleanProp|class.*Prop' -A 5 | head -60Repository: aciderix/gemini-android-app
Length of output: 688
background parameter silently defaults to false if the model sends a non-Boolean value (e.g., string "true").
The schema declares "type": "boolean", but booleanProp() is only a schema builder—it doesn't enforce runtime type validation. The jsonToMap() function preserves JSON types as-is, so call.arguments["background"] could be a Boolean, String, or Number depending on what the API returns. The current cast as? Boolean ?: false treats all non-Boolean values as false, creating a silent failure: a model sending "background": "true" would execute in foreground mode and hit the 12 s Termux timeout, making the failure harder to diagnose.
Consider accepting multiple types:
Suggested fix
- val background = call.arguments["background"] as? Boolean ?: false
+ val background = when (val v = call.arguments["background"]) {
+ is Boolean -> v
+ is String -> v.equals("true", ignoreCase = true)
+ is Number -> v.toInt() != 0
+ else -> false
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val background = call.arguments["background"] as? Boolean ?: false | |
| val background = when (val v = call.arguments["background"]) { | |
| is Boolean -> v | |
| is String -> v.equals("true", ignoreCase = true) | |
| is Number -> v.toInt() != 0 | |
| else -> false | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core-bridge/src/main/kotlin/com/gemini/bridge/tools/ShellTool.kt` at line 50,
The current silent cast of call.arguments["background"] to Boolean in
ShellTool.kt drops non-Boolean JSON values (e.g., "true") and defaults to false;
change the extraction to coerce/validate multiple types instead: read the raw
value from call.arguments["background"], then handle Boolean (use as-is), String
(parse case-insensitive "true"/"false"), and Number (treat 1 as true, 0 as
false), and for any other or unparseable value either log a warning (include the
raw value) and default to a safe choice or throw a clear validation exception;
update the binding where background is defined (the val background assignment
inside ShellTool) and include a short log/validation message referencing the raw
argument so mis-typed inputs are visible at runtime.
Summary
Fixe le faux-fail
exit=-1 — Termux did not replyqu'on voit quandGemini lance un serveur (Python
http.server,live-server,npm run dev,uvicorn…). Le process démarrait bien côté Termux mais le bridgeattendait la fin de la commande, abandonnait à 12 s et renvoyait une
erreur — alors que le serveur tournait toujours.
Fix
run_shell_commandexpose un nouveau paramètrebackground: boolean.Quand il vaut
true, le bridge enveloppe la commande avec :Bash termine immédiatement → Termux broadcast
exit=0dans lamilliseconde → le bridge renvoie un résultat propre avec PID + chemin
du log. Si le process crashe instantanément, on tail les 30 dernières
lignes du log et on retourne
exit=1pour que Gemini sache qu'il y aeu un problème.
Usage depuis Gemini
Le tool description explique explicitement au modèle quand utiliser
background: true(serveurs / watchers / daemons) et commentinspecter / arrêter le process ensuite.
Escapes
Les single quotes dans la commande utilisateur sont correctement
échappées (
'→'\'') pour éviter toute injection quand on ré-enveloppedans
bash -lc '…'.Test plan
background: true→ message[background] started pid=… log=…, serveur accessible surhttp://localhost:8000depuis le navigateur.taildu log depuis Gemini → voit les requêtes HTTP arriver.kill <pid>via Gemini → process stoppé.régression pour
ls,echo, etc.).🤖 Generated with Claude Code
Summary by Sourcery
Add background execution support to Termux shell commands to avoid timeouts for long-running processes and document how to use it from the shell tool.
New Features:
backgroundflag to Termux shell command execution to detach long-running processes, redirect output to per-run log files, and return immediately with PID and log path.Enhancements:
Summary by CodeRabbit