fix(docker): Status detects running containers#27
fix(docker): Status detects running containers#27Gianlu1107 wants to merge 1 commit intotheNetworkChuck:mainfrom
Conversation
# Fix: Docker Container Status Detection Not Working
## 🐛 Problem
The `claude-phone status` command shows:
```
Docker Containers: No containers running
```
But `docker ps` reveals containers ARE running:
```
$ docker ps
CONTAINER ID IMAGE STATUS
abc123 claude-phone_voice-app Up 2 hours
def456 drachtio/drachtio-server Up 2 hours
ghi789 drachtio/drachtio-freeswitch-mrf Up 2 hours
```
## 🔍 Root Cause
**File**: `cli/lib/docker.js:358-399`
**Function**: `getContainerStatus()`
The function executes `docker-compose ps --format json` **without specifying the working directory** (`cwd`). This causes:
1. Docker-compose can't find `docker-compose.yml`
2. Command fails with exit code 1
3. Function silently catches error and returns empty array `[]`
4. UI displays "No containers running" despite containers existing
### Why This Happens
Comparison with other functions in same file:
- ✅ `startContainers()` (line 276): Passes `cwd: configDir`
- ✅ `stopContainers()` (line 330): Passes `cwd: configDir`
- ❌ `getContainerStatus()` (line 369): **Missing `cwd: configDir`**
## ✅ Solution
### 1. Add Missing `cwd` Parameter
```javascript
const child = spawn(compose.cmd, composeArgs, {
cwd: configDir, // 👈 ADDED THIS
stdio: 'pipe'
});
```
### 2. Implement Robust Fallback
If `docker-compose ps --format json` fails:
- Falls back to `docker ps --format "{{.Names}}\t{{.Status}}"`
- Filters for claude-phone related containers (voice-app, drachtio, freeswitch)
- Handles older docker-compose versions without JSON support
### 3. Improve Error Handling
- Proper try-catch blocks instead of silent failures
- Returns meaningful container data even if primary method fails
## 📋 Changes
**File Modified**: `cli/lib/docker.js`
**Lines Changed**: 358-399 (getContainerStatus function)
**Additions**: ~60 lines
**Deletions**: ~30 lines
### Key Changes:
```diff
- const compose = getComposeCommand();
- const composeArgs = [...compose.args, '-f', dockerComposePath, 'ps', '--format', 'json'];
-
- return new Promise((resolve) => {
- const child = spawn(compose.cmd, composeArgs, {
- stdio: 'pipe'
- });
+ const configDir = getConfigDir();
+ const compose = getComposeCommand();
+
+ try {
+ const result = await new Promise((resolve, reject) => {
+ const composeArgs = [...compose.args, '-f', dockerComposePath, 'ps', '--format', 'json'];
+ const child = spawn(compose.cmd, composeArgs, {
+ cwd: configDir, // ✅ FIXED: Added missing cwd
+ stdio: 'pipe'
+ });
+ // ... improved error handling ...
+ });
+ return result;
+ } catch (error) {
+ // Fallback to docker ps
+ // ... fallback implementation ...
+ }
```
## 🧪 Testing
Verified the fix handles:
- ✅ Normal case: docker-compose ps with JSON output
- ✅ Fallback case: docker ps when docker-compose fails
- ✅ Edge case: Older docker-compose without --format json support
- ✅ Error case: Docker socket issues (graceful degradation)
## 📊 Impact
- **Fixes**: `claude-phone status` now correctly shows running containers
- **Backwards Compatible**: No API changes, purely internal fix
- **Robust**: Works with both docker-compose v1 and v2
- **Performance**: Same as before, with fallback option
## 🔗 Related Issues
Fixes the issue: "claude-phone status shows 'No containers running' even when containers are up"
---
**How to Apply This PR**:
1. Review the changes in the `Commits` tab
2. Pull the branch and test locally: `claude-phone status`
3. Verify containers are correctly shown
4. Merge to main when satisfied
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug in the claude-phone status command where it incorrectly reports "No containers running" even when Docker containers are active. The root cause was that the getContainerStatus() function in cli/lib/docker.js was missing the required cwd: configDir parameter when spawning docker-compose commands, causing docker-compose to fail silently when it couldn't locate the docker-compose.yml file.
Changes:
- Fixed
getContainerStatus()to passcwd: configDirwhen spawning docker-compose commands, making it consistent withstartContainers()andstopContainers() - Refactored the function to use async/await pattern with proper error handling
- Added a fallback mechanism using
docker pswhen docker-compose ps fails or doesn't support JSON format
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const child = spawn('docker', ['ps', '--format', '{{.Names}}\t{{.Status}}'], { | ||
| stdio: 'pipe' | ||
| }); | ||
|
|
||
| let output = ''; | ||
| child.stdout.on('data', (data) => { | ||
| output += data.toString(); | ||
| }); | ||
|
|
||
| child.on('close', (code) => { | ||
| if (code === 0) { | ||
| try { | ||
| const lines = output.trim().split('\n').filter(l => l.trim()); | ||
| // Filter for claude-phone related containers | ||
| const containers = lines | ||
| .filter(line => line.match(/voice-app|drachtio|freeswitch/i)) | ||
| .map(line => { | ||
| const [name, ...statusParts] = line.split('\t'); | ||
| return { | ||
| name: name || 'unknown', | ||
| status: statusParts.join('\t') || 'unknown' | ||
| }; | ||
| }); | ||
| resolve(containers); | ||
| } catch (e) { | ||
| resolve([]); | ||
| } | ||
| } else { | ||
| resolve([]); | ||
| } | ||
| }); |
There was a problem hiding this comment.
The fallback docker ps command is missing an error event handler on the child process. If the spawn itself fails (e.g., docker binary not found), this will result in an unhandled error event. Consider adding a child.on('error', () => resolve([])) handler to handle spawn errors gracefully, similar to how the primary try block handles errors on line 411-413.
| if (code === 0 && output.trim()) { | ||
| try { | ||
| // Parse JSON lines (one per container) | ||
| const lines = output.trim().split('\n').filter(l => l.trim()); | ||
| const containers = lines.map(line => { | ||
| const data = JSON.parse(line); | ||
| return { | ||
| name: data.Name || data.Service, | ||
| status: data.State || data.Status | ||
| }; | ||
| }); | ||
| resolve(containers); | ||
| } catch (parseError) { | ||
| reject(new Error('JSON parse failed, falling back')); |
There was a problem hiding this comment.
The condition code === 0 && output.trim() rejects cases where docker-compose ps succeeds but returns no containers (empty output). This differs from the fallback behavior which returns an empty array when successful but no matching containers are found. Consider changing this to code === 0 and handling empty output by resolving with an empty array instead of rejecting, to maintain consistent behavior between the primary and fallback methods.
| if (code === 0 && output.trim()) { | |
| try { | |
| // Parse JSON lines (one per container) | |
| const lines = output.trim().split('\n').filter(l => l.trim()); | |
| const containers = lines.map(line => { | |
| const data = JSON.parse(line); | |
| return { | |
| name: data.Name || data.Service, | |
| status: data.State || data.Status | |
| }; | |
| }); | |
| resolve(containers); | |
| } catch (parseError) { | |
| reject(new Error('JSON parse failed, falling back')); | |
| if (code === 0) { | |
| if (output.trim()) { | |
| try { | |
| // Parse JSON lines (one per container) | |
| const lines = output.trim().split('\n').filter(l => l.trim()); | |
| const containers = lines.map(line => { | |
| const data = JSON.parse(line); | |
| return { | |
| name: data.Name || data.Service, | |
| status: data.State || data.Status | |
| }; | |
| }); | |
| resolve(containers); | |
| } catch (parseError) { | |
| reject(new Error('JSON parse failed, falling back')); | |
| } | |
| } else { | |
| // Successful command but no containers | |
| resolve([]); |
| reject(new Error('JSON parse failed, falling back')); | ||
| } | ||
| } else { | ||
| reject(new Error(`docker compose ps failed (code: ${code})`)); |
There was a problem hiding this comment.
The errorOutput variable is declared and populated but never used. Consider either removing it if it's not needed, or including it in the error message on line 407 to provide more diagnostic information when docker compose ps fails. For example: reject(new Error(\docker compose ps failed (code: ${code}): ${errorOutput}`))`
| reject(new Error(`docker compose ps failed (code: ${code})`)); | |
| reject(new Error(`docker compose ps failed (code: ${code}): ${errorOutput}`)); |
| const lines = output.trim().split('\n').filter(l => l.trim()); | ||
| // Filter for claude-phone related containers | ||
| const containers = lines | ||
| .filter(line => line.match(/voice-app|drachtio|freeswitch/i)) |
There was a problem hiding this comment.
The regex pattern /voice-app|drachtio|freeswitch/i could match unintended containers if there are other containers with these strings in their names. While the claude-phone containers use exact names "voice-app", "drachtio", and "freeswitch", this pattern would also match containers like "my-voice-app" or "test-drachtio". Consider making the pattern more precise, such as /^(voice-app|drachtio|freeswitch)$/i to match only exact names, or checking for a project-specific prefix if docker-compose adds one.
| .filter(line => line.match(/voice-app|drachtio|freeswitch/i)) | |
| .filter(line => { | |
| const [name] = line.split('\t'); | |
| return /^(voice-app|drachtio|freeswitch)$/i.test(name || ''); | |
| }) |
| const [name, ...statusParts] = line.split('\t'); | ||
| return { | ||
| name: name || 'unknown', | ||
| status: statusParts.join('\t') || 'unknown' | ||
| }; |
There was a problem hiding this comment.
The parsing logic assumes that the docker ps output uses tab characters as delimiters. However, the {{.Names}}\t{{.Status}} format template explicitly uses \t, which should produce literal tab characters in the output. Consider testing this parsing logic with actual docker ps output to ensure it handles various status formats correctly (e.g., "Up 2 hours" vs "Up 2 hours (healthy)").
Fix: Docker Container Status Detection Not Working
🐛 Problem
The
claude-phone statuscommand shows:But
docker psreveals containers ARE running:🔍 Root Cause
File:
cli/lib/docker.js:358-399Function:
getContainerStatus()The function executes
docker-compose ps --format jsonwithout specifying the working directory (cwd). This causes:docker-compose.yml[]Why This Happens
Comparison with other functions in same file:
startContainers()(line 276): Passescwd: configDirstopContainers()(line 330): Passescwd: configDirgetContainerStatus()(line 369): Missingcwd: configDir✅ Solution
1. Add Missing
cwdParameter2. Implement Robust Fallback
If
docker-compose ps --format jsonfails:docker ps --format "{{.Names}}\t{{.Status}}"3. Improve Error Handling
📋 Changes
File Modified:
cli/lib/docker.jsLines Changed: 358-399 (getContainerStatus function)
Additions: ~60 lines
Deletions: ~30 lines
Key Changes:
🧪 Testing
Verified the fix handles:
📊 Impact
claude-phone statusnow correctly shows running containers🔗 Related Issues
Fixes the issue: "claude-phone status shows 'No containers running' even when containers are up"
How to Apply This PR:
Commitstabclaude-phone status