Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
# All defaults here work with `docker compose up` out of the box.
#
# Service ports (defaults):
# Postgres → localhost:5432
# Redis → localhost:6379
# Postgres → localhost:5432 (override with PGPORT + DATABASE_URL together)
# Redis → localhost:6379 (override with BUZZ_REDIS_HOST_PORT + REDIS_URL)
# Typesense → localhost:8108
# Adminer → localhost:8082 (DB browser UI)
#
# If Homebrew (or another) Postgres/Redis already owns :5432/:6379, pick free
# host ports and keep the connection URLs in sync, for example:
# PGPORT=55432
# DATABASE_URL=postgres://buzz:buzz_dev@localhost:55432/buzz
# BUZZ_REDIS_HOST_PORT=56379
# REDIS_URL=redis://localhost:56379
#
# Note: If port 8082 conflicts, change the adminer port in docker-compose.yml
# =============================================================================

Expand All @@ -30,6 +37,8 @@ PGDATABASE=buzz
# -----------------------------------------------------------------------------
# Redis 7
# -----------------------------------------------------------------------------
# Host port published by docker-compose (container still listens on 6379).
# BUZZ_REDIS_HOST_PORT=6379
REDIS_URL=redis://localhost:6379

# -----------------------------------------------------------------------------
Expand Down
11 changes: 10 additions & 1 deletion crates/buzz-relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,16 @@ async fn main() -> anyhow::Result<()> {
.git_store
.run_conformance_probe(cfg)
.await
.map_err(|e| anyhow::anyhow!("git conformance probe failed: {e}"))?;
.map_err(|e| {
anyhow::anyhow!(
"git conformance probe failed: {e}. \
Object stores that do not honor S3 If-Match / If-None-Match \
(notably GCS via the S3-interop XML API) cannot satisfy the \
A3 pointer-CAS gate. Use a CAS-capable backend (MinIO, AWS S3), \
or set BUZZ_GIT_CONFORMANCE_PROBE=false to skip admission \
(git ref updates will not be linearizable)."
)
})?;
tracing::info!(
race_width = report.race_width,
race_rounds = report.race_rounds,
Expand Down
1 change: 1 addition & 0 deletions deploy/charts/buzz/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ spec:
- { name: BUZZ_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS, value: {{ .Values.git.packCacheMaxConcurrentPopulations | quote }} }
- { name: BUZZ_GIT_MAX_REPOS_PER_PUBKEY, value: {{ .Values.git.maxReposPerPubkey | quote }} }
- { name: BUZZ_GIT_MAX_CONCURRENT_OPS, value: {{ .Values.git.maxConcurrentOps | quote }} }
- { name: BUZZ_GIT_CONFORMANCE_PROBE, value: {{ .Values.git.conformanceProbe | quote }} }

# ── S3 (non-secret) ──────────────────────────────────────
{{- $s3Endpoint := include "buzz.s3Endpoint" . }}
Expand Down
4 changes: 4 additions & 0 deletions deploy/charts/buzz/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ git:
packCacheVolumeSize: 7Gi # per-pod emptyDir; includes cold-population staging
maxReposPerPubkey: 100
maxConcurrentOps: 20
# Startup A3 probe (S3 conditional-write CAS). Default true. Set false only
# for backends that cannot honor If-Match (e.g. GCS S3-interop) — see #2470.
# Skipping means git pointer updates are not linearizable.
conformanceProbe: true

# ── Migrations ───────────────────────────────────────────────────────────────
# Relay runs sqlx migrations at startup via BUZZ_AUTO_MIGRATE=true.
Expand Down
41 changes: 36 additions & 5 deletions desktop/src-tauri/src/managed_agents/runtime_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,21 @@ pub fn list_managed_agent_runtimes(
.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
let exited_keys: Vec<_> = runtimes
// Reap exited children the same way as `sync_managed_agent_processes` so
// unexpected harness deaths set `last_error` / `last_exit_code` (and the
// Agents UI can show them) instead of silently clearing the runtime
// (#2453). Capture wait results before removing map entries.
let exited: Vec<_> = runtimes
.iter_mut()
.filter_map(|(key, runtime)| match runtime.child.try_wait() {
Ok(Some(_)) | Err(_) => Some(key.clone()),
Ok(Some(status)) => Some((key.clone(), Ok(status), runtime.log_path.clone())),
Err(error) => Some((key.clone(), Err(error), runtime.log_path.clone())),
Ok(None) => None,
})
.collect();
let records_changed = !exited_keys.is_empty();
let records_changed = !exited.is_empty();
let mut statuses = Vec::new();
for key in exited_keys {
for (key, wait_result, log_path) in exited {
runtimes.remove(&key);
super::remove_agent_runtime_receipt(&app, &key);
state.clear_agent_session_cache(&key);
Expand All @@ -179,6 +184,28 @@ pub fn list_managed_agent_runtimes(
{
record.updated_at = crate::util::now_iso();
record.last_stopped_at = Some(record.updated_at.clone());
match wait_result {
Ok(status) => {
record.last_exit_code = status.code();
if status.success() {
// Stopped without our stop command — still a signal.
record.last_error = Some("agent runtime exited unexpectedly".to_string());
record.last_error_code = None;
} else {
let log_err = super::meaningful_agent_error_from_log(&log_path)
.unwrap_or_else(|| super::storage::AgentLogError {
message: format!("harness exited with status {status}"),
code: None,
});
record.last_error = Some(log_err.message);
record.last_error_code = log_err.code;
}
}
Err(error) => {
record.last_error = Some(format!("failed to inspect process state: {error}"));
record.last_error_code = None;
}
}
let status = status_for_with(
&app,
record,
Expand All @@ -194,6 +221,9 @@ pub fn list_managed_agent_runtimes(
statuses.push(status);
}
}
if records_changed {
let _ = app.emit("agents-data-changed", ());
}
statuses.extend(runtimes.iter().filter_map(|(key, runtime)| {
let record = records
.iter()
Expand All @@ -212,7 +242,8 @@ pub fn list_managed_agent_runtimes(
}));
drop(runtimes);
// Records are only mutated above when a runtime exited — skip the store
// rewrite on the common nothing-changed poll.
// rewrite on the common nothing-changed poll. (`agents-data-changed` is
// emitted earlier so the Agents list refreshes alongside status events.)
if records_changed {
save_managed_agents(&app, &records)?;
}
Expand Down
6 changes: 4 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ services:
POSTGRES_DB: buzz
PGDATA: /var/lib/postgresql/data
ports:
- "5432:5432"
# Host port follows PGPORT so local Homebrew Postgres can keep :5432 (#2479).
- "${PGPORT:-5432}:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
Expand All @@ -34,7 +35,8 @@ services:
image: redis:7-alpine
container_name: buzz-redis
ports:
- "6379:6379"
# Host port follows BUZZ_REDIS_HOST_PORT so local Redis can keep :6379 (#2479).
- "${BUZZ_REDIS_HOST_PORT:-6379}:6379"
networks:
- buzz-net
healthcheck:
Expand Down
6 changes: 4 additions & 2 deletions scripts/dev-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ fail_if_local_redis_blocks_compose() {
if docker ps --format '{{.Names}}' | grep -qx 'buzz-redis'; then
return
fi
# Match the compose host publish port (see docker-compose.yml / #2479).
local redis_host_port="${BUZZ_REDIS_HOST_PORT:-6379}"
local redis_pids
redis_pids=$(lsof -nP -iTCP:6379 -sTCP:LISTEN 2>/dev/null | awk 'NR > 1 && $1 == "redis-ser" {print $2}' | sort -u | tr '
redis_pids=$(lsof -nP -iTCP:"${redis_host_port}" -sTCP:LISTEN 2>/dev/null | awk 'NR > 1 && $1 == "redis-ser" {print $2}' | sort -u | tr '
' ' ' || true)
if [[ -n "${redis_pids}" ]]; then
error "Local Redis is already listening on port 6379 (pid(s): ${redis_pids}). Stop it before running setup: brew services stop redis"
error "Local Redis is already listening on port ${redis_host_port} (pid(s): ${redis_pids}). Stop it, or set BUZZ_REDIS_HOST_PORT / REDIS_URL to a free host port before running setup."
exit 1
fi
}
Expand Down