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
67 changes: 67 additions & 0 deletions deploy/test-templates.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,6 +95,73 @@ check_contains "$INSTALL_SH" 'PackName' "install.sh: PackName in PARAM_CFN_NAMES
check_contains "$INSTALL_SH" 'pack_name' "install.sh: pack_name in PARAM_TF_NAMES"
check_contains "$INSTALL_SH" 't4g.medium' "install.sh: hermes default size logic present"

# ── Branch detection & SSM doc version ──────────────────────────────────────
echo -e "${BOLD}Branch & SSM fixes${NC}"
check_contains "$INSTALL_SH" '[[ "$REPO_BRANCH" == "HEAD" ]]' "install.sh: detached HEAD falls back to main"
check_contains "$INSTALL_SH" 'REPO_BRANCH=' "install.sh: REPO_BRANCH is set"
check_contains "$TF_VARS" 'variable "repo_branch"' "TF: repo_branch variable defined"
check_contains "$TF_MAIN" "repo_branch" "TF main: repo_branch passed to userdata template"
check_contains "$TF_USERDATA" 'repo_branch' "TF userdata: uses repo_branch for git clone"
check_contains "$CFN_TEMPLATE" "RepoBranch" "CFN: RepoBranch parameter defined"
check_contains "$INSTALL_SH" "DocumentDescription.DocumentVersion" "install.sh: SSM update-document captures numeric version"
check_contains "$INSTALL_SH" 'new_version' "install.sh: SSM update-document-default-version uses captured version"

echo ""

# ── Branch detection unit tests ─────────────────────────────────────────────
echo -e "${BOLD}Branch detection (unit)${NC}"

# Test: detached HEAD → main
_test_branch="HEAD"
[[ "$_test_branch" == "HEAD" ]] && _test_branch="main"
if [[ "$_test_branch" == "main" ]]; then
pass "Detached HEAD resolves to main"
else
fail "Detached HEAD should resolve to main, got: $_test_branch"
fi

# Test: normal branch → unchanged
_test_branch="installer-ux-overhaul"
[[ "$_test_branch" == "HEAD" ]] && _test_branch="main"
if [[ "$_test_branch" == "installer-ux-overhaul" ]]; then
pass "Normal branch name preserved"
else
fail "Normal branch should be preserved, got: $_test_branch"
fi

# Test: main → unchanged
_test_branch="main"
[[ "$_test_branch" == "HEAD" ]] && _test_branch="main"
if [[ "$_test_branch" == "main" ]]; then
pass "main branch preserved"
else
fail "main should be preserved, got: $_test_branch"
fi

# Test: SSM version regex accepts numeric
_test_version="5"
if [[ "$_test_version" =~ ^[0-9]+$ ]]; then
pass "SSM version regex accepts numeric version"
else
fail "SSM version regex should accept '5'"
fi

# Test: SSM version regex rejects $LATEST
_test_version='$LATEST'
if [[ "$_test_version" =~ ^[0-9]+$ ]]; then
fail "SSM version regex should reject '\$LATEST'"
else
pass "SSM version regex rejects \$LATEST"
fi

# Test: SSM version regex rejects empty
_test_version=""
if [[ -n "$_test_version" && "$_test_version" =~ ^[0-9]+$ ]]; then
fail "SSM version regex should reject empty string"
else
pass "SSM version regex rejects empty string"
fi

echo ""
echo -e "${BOLD}─────────────────────────────────────────────────${NC}"
echo -e " Passed: ${GREEN}${PASS}${NC} Failed: ${RED}${FAIL}${NC}"
Expand Down
19 changes: 12 additions & 7 deletions install.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,6 +126,7 @@ DEPLOY_TERRAFORM=3
INSTALLER_COMMIT="${INSTALLER_COMMIT:-$(git -C "$SCRIPT_DIR" rev-parse --short HEAD 2>/dev/null || echo dev)}"
INSTALLER_DATE="${INSTALLER_DATE:-$(d=$(git -C "$SCRIPT_DIR" log -1 --format='%ci' 2>/dev/null | cut -d' ' -f1,2); echo "${d:-unknown}")}"
REPO_BRANCH="${REPO_BRANCH:-$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)}"
[[ "$REPO_BRANCH" == "HEAD" ]] && REPO_BRANCH="main"

# Detect AWS CloudShell (limited ~1GB home dir, use /tmp for large files)
IS_CLOUDSHELL=false
Expand DownExpand Up@@ -1677,16 +1678,20 @@ ensure_ssm_session_document() {
local doc_content='{"schemaVersion":"1.0","description":"SSM session for Loki - starts as ec2-user and launches TUI","sessionType":"Standard_Stream","inputs":{"runAsEnabled":true,"runAsDefaultUser":"ec2-user","shellProfile":{"linux":"cd ~ && bash --login -c \"loki tui || exec bash --login\""}}}'

if aws ssm describe-document --name "$SSM_DOC_NAME" --region "$DEPLOY_REGION" &>/dev/null; then
# Update existing document to latest version
aws ssm update-document \
# Update existing document and set new version as default
local new_version
new_version=$(aws ssm update-document \
--name "$SSM_DOC_NAME" \
--content "$doc_content" \
--document-version '$LATEST' \
--region "$DEPLOY_REGION" >/dev/null 2>&1 || true
aws ssm update-document-default-version \
--name "$SSM_DOC_NAME" \
--document-version '$LATEST' \
--region "$DEPLOY_REGION" >/dev/null 2>&1 || true
--region "$DEPLOY_REGION" \
--query 'DocumentDescription.DocumentVersion' --output text 2>/dev/null) || true
if [[ -n "$new_version" && "$new_version" =~ ^[0-9]+$ ]]; then
aws ssm update-document-default-version \
--name "$SSM_DOC_NAME" \
--document-version "$new_version" \
--region "$DEPLOY_REGION" >/dev/null 2>&1 || true
fi
ok "SSM session document: ${SSM_DOC_NAME} (updated)"
return 0
fi
Expand Down
87 changes: 87 additions & 0 deletions upstream-fixes.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
# Upstream Fixes Needed: OpenClaw + pi-coding-agent Bedrock Auth

OpenClaw + pi-coding-agent fail when using AWS Bedrock with EC2 instance-profile auth (IMDS).
The AWS SDK credential chain works fine, but the auth pre-flight checks reject it before any request is made.

We currently patch these at install time (`packs/openclaw/resources/patch-pi-agent.py`), but
these patches are overwritten on every OpenClaw update.

## Issue 1: pi-coding-agent `hasConfiguredAuth()` rejects AWS SDK auth

**File:** `src/core/model-registry.ts` (dist: `dist/core/model-registry.js`)

**Problem:** `hasConfiguredAuth()` only checks `authStorage.hasAuth()` and `providerRequestConfigs.apiKey`. Neither is set for AWS SDK auth via instance profile. This causes "No API key found for amazon-bedrock" before any API request is made.

**Fix:** Return `true` when the provider is `amazon-bedrock` and the config uses `auth: "aws-sdk"`.

```typescript
// In hasConfiguredAuth():
if (this.providerId === "amazon-bedrock") return true;
```

**Why:** Bedrock auth uses AWS SDK signing (SigV4), not API keys. The SDK resolves credentials from the instance metadata service (IMDS) at request time. There's no key to pre-check.

---

## Issue 2: pi-coding-agent `_getRequiredRequestAuth()` throws on undefined apiKey

**File:** `src/core/agent-session.ts` (dist: `dist/core/agent-session.js`)

**Problem:** `_getRequiredRequestAuth()` throws when `getApiKeyAndHeaders()` returns `ok: true` but no `apiKey`. Bedrock uses AWS SDK signing (no API key needed), but this code path doesn't account for it.

**Fix:** Early return for `amazon-bedrock` provider with `{ apiKey: undefined, headers }`.

```typescript
// In _getRequiredRequestAuth():
if (this.providerId === "amazon-bedrock") {
const authResult = await this._getRequestAuth?.() || { ok: true, headers: {} };
return { apiKey: undefined, headers: authResult.headers || {} };
}
```

**Why:** The Bedrock provider adapter handles auth via the AWS SDK at the HTTP layer (SigV4 signing). It never needs an API key injected by the session.

---

## Issue 3 (Root Cause): OpenClaw auth-controller doesn't inject SDK auth into pi's authStorage

**File:** `src/agents/pi-embedded-runner/run/auth-controller.ts`

**Problem:** OpenClaw's auth controller correctly resolves AWS SDK auth (returns `{ mode: "aws-sdk", source: "..." }` from `resolveAwsSdkAuthInfo()`), but does an early return at ~line 329-337 without calling `setRuntimeApiKey()`. This means pi's `authStorage` never learns about Bedrock credentials.

**Proper fix:** When auth mode is `aws-sdk`, the auth controller should either:
1. Call `setRuntimeApiKey()` with a sentinel value that pi recognizes as "use SDK signing", or
2. Set a flag on the provider config that pi checks in `hasConfiguredAuth()` and `_getRequiredRequestAuth()`

This would eliminate the need for patches #1 and #2.

---

## Issue 4: `resolveAwsSdkEnvVarName()` doesn't detect IMDS

**File:** `src/agents/model-auth-runtime-shared.ts`

**Problem:** `resolveAwsSdkEnvVarName()` checks for `AWS_BEARER_TOKEN_BEDROCK`, `AWS_ACCESS_KEY_ID`+`AWS_SECRET_ACCESS_KEY`, and `AWS_PROFILE`. If none are set, it returns `undefined`. EC2 instances with IAM roles don't need any env vars — the SDK discovers credentials via IMDS automatically.

**Current workaround:** We set `AWS_PROFILE=default` in `/etc/profile.d/` and the systemd service unit, which triggers the env var check and lets the SDK fall through to IMDS.

**Proper fix:** The fallback in `resolveAwsSdkAuthInfo()` already returns `{ mode: "aws-sdk", source: "aws-sdk default chain" }` when no env vars are found — this is correct. The issue is upstream in the auth controller (Issue 3) not propagating this to pi.

---

## Environment

- OpenClaw version: latest (as of 2026-04-06)
- pi-coding-agent version: 0.65.0
- Platform: Amazon Linux 2023 on EC2 with IAM instance profile
- Auth method: Bedrock via IMDS (no API keys, no env vars)
- Config: `models.providers.amazon-bedrock.auth: "aws-sdk"`

## Reproduction

1. Launch EC2 instance with IAM role that has Bedrock access
2. Install OpenClaw: `npm install -g openclaw`
3. Configure with `auth: "aws-sdk"` for amazon-bedrock provider
4. Run `openclaw tui` — fails with "No API key found for amazon-bedrock"
5. Set `AWS_PROFILE=default` — still fails (auth pre-flight in pi rejects it)
6. Apply patches to model-registry.js and agent-session.js — works
Loading