fix(spawn): strip ANSI codes from zellij list-sessions output

Zellij outputs colored text with ANSI escape codes, which caused
session name parsing to fail. Now strips escape codes before parsing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-26 17:20:21 -05:00
parent f26e9acb34
commit 69175f08f9
13 changed files with 16 additions and 6 deletions

View File

@@ -0,0 +1 @@
[ 94144ms] [ERROR] Failed to load resource: the server responded with a status of 500 (Internal Server Error) @ http://127.0.0.1:7400/api/spawn:0

View File

@@ -0,0 +1 @@
[ 398ms] [WARNING] cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation @ https://cdn.tailwindcss.com/:63

View File

@@ -99,9 +99,11 @@ class SpawnMixin:
try:
# Check rate limit inside lock
# Use None sentinel to distinguish "never spawned" from "spawned at time 0"
# (time.monotonic() can be close to 0 on fresh process start)
now = time.monotonic()
last_spawn = _spawn_timestamps.get(project, 0)
if now - last_spawn < SPAWN_COOLDOWN_SEC:
last_spawn = _spawn_timestamps.get(project)
if last_spawn is not None and now - last_spawn < SPAWN_COOLDOWN_SEC:
remaining = SPAWN_COOLDOWN_SEC - (now - last_spawn)
self._send_json(429, {
'ok': False,
@@ -177,8 +179,11 @@ class SpawnMixin:
)
if result.returncode != 0:
return False
# Strip ANSI escape codes (Zellij outputs colored text)
ansi_pattern = re.compile(r'\x1b\[[0-9;]*m')
output = ansi_pattern.sub('', result.stdout)
# Parse line-by-line to avoid substring false positives
for line in result.stdout.splitlines():
for line in output.splitlines():
# Zellij outputs "session_name [Created ...]" or just "session_name"
session_name = line.strip().split()[0] if line.strip() else ''
if session_name == ZELLIJ_SESSION:

View File

@@ -75,12 +75,15 @@ export function SpawnModal({ isOpen, onClose, onSpawn, currentProject }) {
}, [isOpen, handleClose]);
const handleSpawn = async () => {
const project = currentProject || selectedProject;
if (!project) {
const rawProject = currentProject || selectedProject;
if (!rawProject) {
setError('Please select a project');
return;
}
// Extract project name from full path (sidebar passes projectDir like "/Users/.../projects/amc")
const project = rawProject.includes('/') ? rawProject.split('/').pop() : rawProject;
setLoading(true);
setError(null);
@@ -169,7 +172,7 @@ export function SpawnModal({ isOpen, onClose, onSpawn, currentProject }) {
<div class="flex flex-col gap-1.5">
<label class="text-label font-medium text-dim">Project</label>
<div class="rounded-xl border border-selection/75 bg-bg/70 px-3 py-2 text-sm text-bright">
${currentProject}
${currentProject.includes('/') ? currentProject.split('/').pop() : currentProject}
</div>
</div>
`}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB