mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 08:36:35 +03:00
* fix: diagnose AppArmor userns block on bwrap probe (#351) Ubuntu 24.04+ ships apparmor_restrict_unprivileged_userns=1 by default, which blocks the user namespace bwrap needs to start. The daemon's probe then fails, auto-detect silently falls through to KVM, and KVM hangs waiting for a rootfs the user hasn't set up — leaving Cowork stuck in a retry loop with no clear error. - Classify the probe failure (classifyBwrapProbeError) so the daemon can distinguish AppArmor/userns blocks from generic failures and log a pointer to the TROUBLESHOOTING.md remediation. - Stop falling through to KVM when bwrap is installed but blocked; drop to host-direct instead so users see a working (if unsandboxed) Cowork and the reason bwrap didn't engage. Users who actually want KVM can still set COWORK_VM_BACKEND=kvm. - Mirror the probe + diagnosis in `--doctor` so misconfigured systems get the same actionable output without waiting for a daemon log. - Document the AppArmor profile workaround in TROUBLESHOOTING.md. - Credit @hfyeh for the diagnosis and profile snippet. Co-Authored-By: Claude <claude@anthropic.com> * refactor: simplify PR #434 per cdd-code-simplifier Drop redundant `-n` guard around the COWORK_VM_BACKEND case in `--doctor`: the `${VAR,,}` expansion is already safe on an unset var (no `set -u` in this script) and the `kvm|host` arms simply don't match an empty string. Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
126
tests/cowork-backend-detection.bats
Normal file
126
tests/cowork-backend-detection.bats
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bats
|
||||
#
|
||||
# cowork-backend-detection.bats
|
||||
# Tests for classifyBwrapProbeError — diagnoses why the bwrap sandbox
|
||||
# probe failed so the daemon can emit actionable errors instead of
|
||||
# silently falling through to a broken KVM backend (issue #351).
|
||||
#
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME}")" && pwd)"
|
||||
|
||||
NODE_PREAMBLE='
|
||||
const {
|
||||
classifyBwrapProbeError,
|
||||
} = require("'"${SCRIPT_DIR}"'/../scripts/cowork-vm-service.js");
|
||||
|
||||
function assert(condition, msg) {
|
||||
if (!condition) {
|
||||
process.stderr.write("ASSERTION FAILED: " + msg + "\n");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, msg) {
|
||||
assert(actual === expected,
|
||||
msg + " expected=" + JSON.stringify(expected) +
|
||||
" actual=" + JSON.stringify(actual));
|
||||
}
|
||||
|
||||
function mkErr(stderr, message) {
|
||||
return {
|
||||
message: message || "Command failed",
|
||||
stderr: Buffer.from(stderr || ""),
|
||||
stdout: Buffer.from(""),
|
||||
};
|
||||
}
|
||||
'
|
||||
|
||||
# =============================================================================
|
||||
# classifyBwrapProbeError — AppArmor / userns denials (the #351 case)
|
||||
# =============================================================================
|
||||
|
||||
@test "classifyBwrapProbeError: bwrap EPERM on user namespace" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('bwrap: Creating new user namespace: Operation not permitted');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'userns', 'EPERM on userns should classify as userns');
|
||||
assert(r.stderr.includes('user namespace'), 'stderr is preserved');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: AppArmor denial message" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('bwrap: setting up uid map: Permission denied');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'userns', 'uid map denial should classify as userns');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: explicit apparmor keyword" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('denied by AppArmor policy');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'userns', 'apparmor keyword should classify as userns');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: CLONE_NEWUSER keyword in kernel log" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('bwrap: unshare: CLONE_NEWUSER failed: EPERM');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'userns', 'CLONE_NEW* should classify as userns');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: CAP_SYS_ADMIN hint" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('need CAP_SYS_ADMIN to create user namespace');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'userns', 'CAP_SYS_ADMIN hint should classify as userns');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# classifyBwrapProbeError — non-userns failures
|
||||
# =============================================================================
|
||||
|
||||
@test "classifyBwrapProbeError: unrelated bwrap failure" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = mkErr('bwrap: No such file or directory: /does-not-exist');
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'unknown', 'unrelated errors should classify as unknown');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: spawn ENOENT has no stderr" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const e = { message: 'spawn bwrap ENOENT', code: 'ENOENT' };
|
||||
const r = classifyBwrapProbeError(e);
|
||||
assertEqual(r.kind, 'unknown', 'ENOENT without userns text is unknown');
|
||||
assertEqual(r.stderr, '', 'missing stderr normalized to empty string');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: empty error object" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const r = classifyBwrapProbeError({});
|
||||
assertEqual(r.kind, 'unknown', 'empty error is unknown, not a crash');
|
||||
assertEqual(r.stderr, '', 'missing stderr normalized to empty string');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
|
||||
@test "classifyBwrapProbeError: null-safe" {
|
||||
run node -e "${NODE_PREAMBLE}
|
||||
const r = classifyBwrapProbeError(null);
|
||||
assertEqual(r.kind, 'unknown', 'null error does not crash');
|
||||
"
|
||||
[[ "$status" -eq 0 ]]
|
||||
}
|
||||
Reference in New Issue
Block a user