Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ on:
workflow_dispatch:

env:
# Force wrapper mode (not async/daemon) so post-commit hooks fire
# synchronously and attribution notes are written in-process.
GIT_AI_ASYNC_MODE: "false"
# Async mode is now fully supported by the e2e tests: a per-test daemon
# is started in setup() and the wrapper polls for authorship notes.
GIT_AI_ASYNC_MODE: "true"

jobs:
e2e-tests:
Expand Down
23 changes: 20 additions & 3 deletions .github/workflows/install-scripts-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ on:
workflow_dispatch:

env:
# Force wrapper mode (not async/daemon) so post-commit hooks fire
# synchronously and attribution notes are written in-process.
GIT_AI_ASYNC_MODE: "false"
# Async mode: each Unix job starts a per-job daemon via
# start-async-daemon.sh which exports GIT_AI_TEST_FORCE_TTY and
# GIT_AI_POST_COMMIT_TIMEOUT_MS so the wrapper polls for authorship
# notes after commits. Windows jobs override this to "false" because
# Unix-domain sockets are not available there.
GIT_AI_ASYNC_MODE: "true"

jobs:
install-local-unix:
Expand Down Expand Up @@ -135,6 +138,11 @@ jobs:
cd /tmp/e2e-test-repo
"$INSTALL_DIR/git-ai" install

- name: Start async daemon
run: |
source "$GITHUB_WORKSPACE/scripts/nightly/start-async-daemon.sh" \
"$HOME/.git-ai/bin/git-ai"

- name: Run synthetic agent commit
env:
RESULTS_DIR: /tmp/e2e-results/${{ matrix.agent.name }}
Expand All @@ -151,6 +159,12 @@ jobs:
bash "$GITHUB_WORKSPACE/scripts/nightly/verify-synthetic-attribution.sh" \
"${{ matrix.agent.name }}" /tmp/e2e-test-repo

- name: Stop async daemon
if: always()
run: |
bash "$GITHUB_WORKSPACE/scripts/nightly/stop-async-daemon.sh" \
"$HOME/.git-ai/bin/git-ai"

- name: Upload E2E test results
if: always()
uses: actions/upload-artifact@v4
Expand All @@ -163,6 +177,9 @@ jobs:
install-local-windows:
name: E2E ${{ matrix.agent.name }} on windows-latest
runs-on: windows-latest
env:
# Windows does not support Unix-domain sockets for the async daemon.
GIT_AI_ASYNC_MODE: "false"
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
Expand Down
30 changes: 27 additions & 3 deletions .github/workflows/nightly-agent-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ on:
env:
GIT_AI_DEBUG: "1"
CARGO_INCREMENTAL: "0"
# Force wrapper mode (not async/daemon) so post-commit hooks fire
# synchronously and attribution notes are written in-process.
GIT_AI_ASYNC_MODE: "false"
# Async mode: each job starts a per-job daemon via
# start-async-daemon.sh which exports GIT_AI_TEST_FORCE_TTY and
# GIT_AI_POST_COMMIT_TIMEOUT_MS so the wrapper polls for authorship
# notes after commits.
GIT_AI_ASYNC_MODE: "true"

jobs:
# ── Version Resolution ─────────────────────────────────────────────────────
Expand Down Expand Up @@ -202,6 +204,11 @@ jobs:
cd /tmp/test-repo
git-ai install

- name: Start async daemon
run: |
source "$GITHUB_WORKSPACE/scripts/nightly/start-async-daemon.sh" \
"$GITHUB_WORKSPACE/target/release/git-ai"

- name: Verify hook wiring
run: |
export PATH="$GITHUB_WORKSPACE/target/release:$PATH"
Expand All @@ -215,6 +222,12 @@ jobs:
"${{ matrix.agent }}" \
/tmp/test-repo

- name: Stop async daemon
if: always()
run: |
bash "$GITHUB_WORKSPACE/scripts/nightly/stop-async-daemon.sh" \
"$GITHUB_WORKSPACE/target/release/git-ai"

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -289,6 +302,11 @@ jobs:
cd /tmp/test-repo
git-ai install

- name: Start async daemon
run: |
source "$GITHUB_WORKSPACE/scripts/nightly/start-async-daemon.sh" \
"$GITHUB_WORKSPACE/target/release/git-ai"

- name: Run live agent test (with retry)
uses: nick-fields/retry@v2
with:
Expand All @@ -311,6 +329,12 @@ jobs:
bash "$GITHUB_WORKSPACE/scripts/nightly/verify-attribution.sh" "${{ matrix.agent }}"
continue-on-error: ${{ matrix.channel == 'latest' }}

- name: Stop async daemon
if: always()
run: |
bash "$GITHUB_WORKSPACE/scripts/nightly/stop-async-daemon.sh" \
"$GITHUB_WORKSPACE/target/release/git-ai"

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
Expand Down
124 changes: 124 additions & 0 deletions scripts/nightly/start-async-daemon.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Start the git-ai async daemon for CI workflows.
#
# Usage: source scripts/nightly/start-async-daemon.sh <git-ai-binary> [real-git-path]
#
# The script:
# 1. Creates/updates ~/.git-ai/config.json with async_mode enabled
# 2. Picks socket paths under RUNNER_TEMP (or /tmp)
# 3. Starts the daemon in the background
# 4. Waits for sockets to appear (up to 10 s)
# 5. Exports env vars to GITHUB_ENV so subsequent steps inherit them
#
# After sourcing, the following env vars are set in the current shell AND
# appended to GITHUB_ENV (if it exists):
# GIT_AI_ASYNC_MODE, GIT_AI_TEST_FORCE_TTY, GIT_AI_POST_COMMIT_TIMEOUT_MS,
# GIT_AI_DAEMON_HOME, GIT_AI_DAEMON_CONTROL_SOCKET, GIT_AI_DAEMON_TRACE_SOCKET,
# ASYNC_DAEMON_PID
set -euo pipefail

GIT_AI_BIN="${1:?Usage: source start-async-daemon.sh <path-to-git-ai-binary> [real-git-path]}"

# ── Locate real git (not the git-ai proxy) ───────────────────────────────────
# The caller can pass an explicit path; otherwise probe common locations so we
# never accidentally point the daemon config at the git-ai proxy symlink.
REAL_GIT="${2:-}"
if [ -z "$REAL_GIT" ]; then
for candidate in /usr/bin/git /usr/local/bin/git; do
if [ -x "$candidate" ]; then
REAL_GIT="$candidate"
break
fi
done
# Last resort: use whatever is on PATH.
if [ -z "$REAL_GIT" ]; then
REAL_GIT="$(command -v git)"
fi
fi

# ── Daemon home directory ────────────────────────────────────────────────────
DAEMON_HOME="${RUNNER_TEMP:-/tmp}/git-ai-daemon-$$"
mkdir -p "$DAEMON_HOME/.git-ai"

# ── Write daemon config ──────────────────────────────────────────────────────
cat > "$DAEMON_HOME/.git-ai/config.json" <<CONF
{
"git_path": "$REAL_GIT",
"disable_auto_updates": true,
"feature_flags": {
"async_mode": true,
"git_hooks_enabled": false
},
"quiet": false
}
CONF

# Also ensure the actual HOME's config has async_mode (some steps read from HOME)
if [ -d "$HOME/.git-ai" ]; then
if command -v python3 >/dev/null 2>&1; then
python3 -c "
import json, os, sys
cfg_path = os.path.join(os.environ['HOME'], '.git-ai', 'config.json')
if not os.path.exists(cfg_path):
sys.exit(0)
with open(cfg_path) as f:
cfg = json.load(f)
ff = cfg.setdefault('feature_flags', {})
ff['async_mode'] = True
with open(cfg_path, 'w') as f:
json.dump(cfg, f, indent=2)
" 2>/dev/null || true
fi
fi

# ── Socket paths ─────────────────────────────────────────────────────────────
CTRL_SOCK="$DAEMON_HOME/control.sock"
TRACE_SOCK="$DAEMON_HOME/trace.sock"

# ── Export env vars ──────────────────────────────────────────────────────────
export GIT_AI_ASYNC_MODE=true
export GIT_AI_TEST_FORCE_TTY=1
export GIT_AI_POST_COMMIT_TIMEOUT_MS=30000
export GIT_AI_DAEMON_HOME="$DAEMON_HOME"
export GIT_AI_DAEMON_CONTROL_SOCKET="$CTRL_SOCK"
export GIT_AI_DAEMON_TRACE_SOCKET="$TRACE_SOCK"

# Persist to GITHUB_ENV so subsequent workflow steps inherit them.
if [ -n "${GITHUB_ENV:-}" ]; then
{
echo "GIT_AI_ASYNC_MODE=true"
echo "GIT_AI_TEST_FORCE_TTY=1"
echo "GIT_AI_POST_COMMIT_TIMEOUT_MS=30000"
echo "GIT_AI_DAEMON_HOME=$DAEMON_HOME"
echo "GIT_AI_DAEMON_CONTROL_SOCKET=$CTRL_SOCK"
echo "GIT_AI_DAEMON_TRACE_SOCKET=$TRACE_SOCK"
} >> "$GITHUB_ENV"
fi

# ── Start the daemon ─────────────────────────────────────────────────────────
"$GIT_AI_BIN" bg run &
ASYNC_DAEMON_PID=$!
export ASYNC_DAEMON_PID

if [ -n "${GITHUB_ENV:-}" ]; then
echo "ASYNC_DAEMON_PID=$ASYNC_DAEMON_PID" >> "$GITHUB_ENV"
fi

# ── Wait for sockets (up to 10 s) ───────────────────────────────────────────
for _i in $(seq 1 400); do
[ -S "$CTRL_SOCK" ] && [ -S "$TRACE_SOCK" ] && break
sleep 0.025
done

if [ ! -S "$CTRL_SOCK" ] || [ ! -S "$TRACE_SOCK" ]; then
echo "ERROR: daemon sockets did not appear after 10 s" >&2
echo " CTRL_SOCK=$CTRL_SOCK" >&2
echo " TRACE_SOCK=$TRACE_SOCK" >&2
kill -9 "$ASYNC_DAEMON_PID" 2>/dev/null || true
exit 1
fi

echo "Async daemon started (PID=$ASYNC_DAEMON_PID)"
echo " DAEMON_HOME=$DAEMON_HOME"
echo " CTRL_SOCK=$CTRL_SOCK"
echo " TRACE_SOCK=$TRACE_SOCK"
37 changes: 37 additions & 0 deletions scripts/nightly/stop-async-daemon.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Gracefully stop the git-ai async daemon started by start-async-daemon.sh.
#
# Usage: bash scripts/nightly/stop-async-daemon.sh [git-ai-binary]
#
# Reads ASYNC_DAEMON_PID, GIT_AI_DAEMON_HOME, and socket paths from env.
# Falls back to kill -9 if graceful shutdown times out.
set -uo pipefail

GIT_AI_BIN="${1:-}"

if [ -z "${ASYNC_DAEMON_PID:-}" ]; then
echo "No ASYNC_DAEMON_PID set — nothing to stop."
exit 0
fi

# Try graceful shutdown via the control socket.
if [ -n "$GIT_AI_BIN" ] && [ -S "${GIT_AI_DAEMON_CONTROL_SOCKET:-}" ]; then
"$GIT_AI_BIN" bg shutdown 2>/dev/null || true
fi

# Wait up to 2 s for the process to exit.
for _i in $(seq 1 40); do
kill -0 "$ASYNC_DAEMON_PID" 2>/dev/null || break
sleep 0.05
done

# Force-kill if still alive.
kill -9 "$ASYNC_DAEMON_PID" 2>/dev/null || true
wait "$ASYNC_DAEMON_PID" 2>/dev/null || true

# Clean up daemon home.
if [ -n "${GIT_AI_DAEMON_HOME:-}" ] && [ -d "$GIT_AI_DAEMON_HOME" ]; then
rm -rf "$GIT_AI_DAEMON_HOME"
fi

echo "Async daemon stopped (PID=$ASYNC_DAEMON_PID)."
Loading
Loading