Skip to content

fix(cron): add Authorization header to provider preflight probe#83648

Open
samson1357924 wants to merge 9 commits into
openclaw:mainfrom
samson1357924:fix/cron-preflight-clean
Open

fix(cron): add Authorization header to provider preflight probe#83648
samson1357924 wants to merge 9 commits into
openclaw:mainfrom
samson1357924:fix/cron-preflight-clean

Conversation

@samson1357924

@samson1357924 samson1357924 commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaced the hand-rolled Authorization header logic in the cron preflight probe with the shared resolveProviderRequestHeaders() from provider-request-config.ts, and extended it to pass providerConfig.headers through the same pipeline. This ensures the preflight and normal model request paths build identical auth headers for the same config, including provider-level static headers and request-level custom headers.

Changes

P1: Reuse shared auth normalization (ClawSweeper finding)

  • model-preflight.runtime.ts: Replaced the manual if (params.apiKey) { headers["Authorization"] = ... } / custom-header construction with delegation to resolveProviderRequestHeaders() via sanitizeConfiguredProviderRequest()
  • Auth is now resolved through the same code path as normal model requests — any future changes to auth formatting apply automatically
  • Full credential chain preserved: request.auth override → resolveApiKeyForProvider fallback → no auth
  • Also ensures request.headers are always passed through (even without request.auth), fixing a pre-existing gap where provider-level and request-level headers were silently dropped

P1: Provider-level header parity (ClawSweeper finding)

  • model-preflight.runtime.ts: Added sanitizeModelHeaders(providerConfig.headers) and passes the result as defaultHeaders to resolveProviderRequestHeaders, matching the normal model request path in model.ts:675
  • Provider-level static headers (models.providers.*.headers) now flow into preflight probes — previously only request.auth and request.headers were forwarded
  • Both resolveProviderRequestHeaders calls (primary and resolveApiKeyForProvider fallback) include defaultHeaders for defense-in-depth
  • Tests: 8 new tests cover provider headers, merging with auth, secret-ref stripping, non-string filtering, and undefined/empty boundary cases

P1: Preserve request.headers in fallback auth call (ClawSweeper finding)

  • model-preflight.runtime.ts: Changed fallback resolveProviderRequestHeaders call from request: { auth: { ... } } to request: { ...requestOverrides, auth: { ... } } so sanitized request.headers survive when env/profile credentials are used
  • Previously the fallback rebuilt the request override as auth-only, dropping any proxy/tenant headers from models.providers.*.request.headers
  • Matches normal model request behavior where request.headers and request.auth coexist

P1: Treat provider-default auth mode as fallback-triggering (ClawSweeper finding)

  • model-preflight.runtime.ts: Changed fallback gate from !requestOverrides?.auth to also skip provider-default mode — when request.auth.mode: "provider-default", the sanitizer returns an auth object that blocks the fallback, but the shared resolver does not inject headers for provider-default, leaving preflight unauthenticated
  • Fix: hasExplicitAuth = requestOverrides?.auth && requestOverrides.auth.mode !== "provider-default" — provider-default now falls through to credential lookup
  • Tests: 2 new tests cover provider-default + fallback, with and without request.headers
  • Mock: added provider-default handling to sanitizeConfiguredProviderRequestMock to match real sanitizer output

P1: Sanitized auth state for fallback gate

  • model-preflight.runtime.ts: Replaced raw config check (headerModeConfigured from providerConfig?.request?.auth?.mode) with sanitized check (!requestOverrides?.auth)
  • Fixes: misconfigured mode:header (empty headerName/value) no longer incorrectly blocks the resolveApiKeyForProvider fallback
  • Fixes: provider-level headers no longer make headers truthy and erroneously skip fallback
  • Removed stale debug log block re-reading raw config

P0: Error redaction at cache-write time

  • model-preflight.runtime.ts: Applied formatErrorMessage() to errors before storing in preflightCache, preventing unredacted error objects from remaining in heap between cache write and read

P0: Header prefix trimming consistency

  • Prefix is now trimmed via shared sanitizeConfiguredProviderRequest, matching the behavior of resolveAuthOverride in the normal request path

Cross-path auth + provider header tests

  • model-preflight.runtime.test.ts: 4 parity tests (P0) verifying preflight and normal path produce identical auth headers for the same config
  • 4 edge case tests (P1): empty header fields, unknown mode, whitespace token, OAuth marker filtering
  • 8 provider-level header tests (P1): basic pass-through, merge with auth, without auth, secret ref stripping, non-string filtering, boundary cases
  • 5 fallback integration tests (P1): provider-level headers through fallback, request.headers preserved through fallback, empty headerName/value fallthrough
  • 2 provider-default fallback tests (P1): provider-default + resolveApiKeyForProvider, with request.headers preservation
  • Total: 42 tests pass

Files Changed

File Δ
src/cron/isolated-agent/model-preflight.runtime.ts +79/-5
src/cron/isolated-agent/model-preflight.runtime.test.ts +1510/-1
src/cron/isolated-agent/run.ts +2/-0

Real behavior proof

Behavior or issue addressed: Cron job preflight was building custom auth headers (request.auth.mode === "header") with hand-rolled logic that missed prefix trimming, header collision cleanup, and request.headers merging — inconsistent with the shared auth normalization used by normal model requests.

The fix replaces the entire preflight auth resolution with delegation to resolveProviderRequestHeaders(), the same shared function used by the normal model request path. This ensures:

  • Authorization header and custom-header auth are built identically in both paths
  • Prefix trimming and header collision cleanup apply automatically
  • request.headers are always forwarded (even without request.auth)
  • Provider-level headers (models.providers.*.headers) now pass through via defaultHeaders, matching the normal model path
  • request.headers are preserved in the fallback credential path, so proxy/tenant headers are not lost

Real environment tested:

Exact steps or command run after this patch (original base auth case):

node -e "const m = require('./dist/index.cjs'); m.probeCronModelProvider({ provider: 'litellm', model: 'gpt-4o' }).then(r => console.log(JSON.stringify(r)))"

Evidence after fix: LiteLLM proxy logs before (top) and after (bottom) the patch show GET /models now gets 200 OK instead of 401:

Before/after terminal comparison

Before — no auth header, cron preflight triggers 401:

LiteLLM Proxy:ERROR - Exception occured - No api key passed in.

After — auth header sent, preflight succeeds:

127.0.0.1:45914 - "GET /models HTTP/1.1" 200 OK

The comprehensive auth normalization refactoring (prefix trim, header merging, parity with normal path, provider-header support) is covered by the test suite below.

Additional verification (unit test suite):

 ✓ src/cron/isolated-agent/model-preflight.runtime.test.ts (42 tests)
 ✓ src/agents/provider-request-config.test.ts (30 tests)
All 72 tests pass across both files. No regressions.

Coverage includes:

  • 4 cross-path auth header parity tests (preflight headers match normal path headers)
  • 4 edge case tests (empty header fields, unknown mode, whitespace token, OAuth marker)
  • 8 provider-level header tests (basic, merge with auth, without auth, secret ref stripping, non-string filtering, undefined/empty boundaries)
  • 5 fallback integration tests (provider-level headers through fallback, request.headers through fallback, empty headerName/value fallthrough)
  • Existing bearer auth, header mode, and fallback tests

Observed result after fix:

  • Live LiteLLM probe: GET /models changed from 401 Unauthorized to 200 OK
  • Unit tests: 42/42 pass (preflight), 30/30 pass (regression)
  • Auth delegation is correct with proper fallback semantics and edge case handling
  • Provider-level headers (providerConfig.headers) pass through sanitizeModelHeaders with secret-ref marker filtering, matching normal model path

What was not tested:

  • Integration test with actual local provider endpoint (preflight tests mock fetchWithSsrFGuard)
  • prepareProviderRuntimeAuth plugin auth exchange path (documented gap — preflight intentionally skips this for local provider probes)
  • Provider-level headers containing unresolved SecretRef objects — these are non-string config shapes that sanitizeModelHeaders silently skips, and preflight runs only after config validation

@openclaw-barnacle openclaw-barnacle Bot added size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 18, 2026
@clawsweeper

clawsweeper Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 3:05 PM ET / 19:05 UTC.

Summary
The PR changes isolated cron model-provider preflight to build probe headers through shared provider request/auth helpers, pass agent/workspace dirs into credential lookup, and add auth/header regression tests.

PR surface: Source +80, Tests +1505. Total +1585 across 3 files.

Reproducibility: yes. at source level: current main and v2026.6.11 send the local cron preflight GET without configured provider/request headers. The inspected LiteLLM screenshot shows the bearer-header path changing from unauthenticated 401 to authenticated 200, but I did not run a live LiteLLM setup in this read-only review.

Review metrics: 1 noteworthy metric.

  • Credentialed Preflight Probe: 1 runtime probe changed. Cron preflight changes from a bare local-provider reachability GET to a configured header-bearing request, which matters for compatibility and credential scope before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Get explicit maintainer acceptance that cron local/private preflight should send configured provider/request credentials before merge.

Risk before merge

  • [P1] The PR intentionally changes cron local/private preflight from a bare reachability GET to a configured header-bearing request, so provider credentials can reach the endpoint before the normal model runner starts.
  • [P1] The inspected live proof covers the bearer-header LiteLLM path; provider-default, request.headers, and provider-level header parity are source/test-backed rather than live-provider-backed.

Maintainer options:

  1. Accept Credentialed Local Preflight (recommended)
    If maintainers agree cron preflight should use the same configured provider/request headers as model requests, this PR is a reasonable landing path after normal checks.
  2. Pause For Probe Policy Review
    If credentials must remain runner-only or opt-in for local/private probes, pause this PR and decide the preflight credential boundary before merge.
  3. Ask For Broader Live Proof
    Maintainers can request additional redacted LiteLLM or vLLM proof for provider-default and request-header configurations if unit coverage is not enough for the boundary change.

Next step before merge

  • [P2] The remaining action is maintainer acceptance of the credentialed preflight boundary and normal merge validation, not an automated code repair.

Security
Cleared: No concrete supply-chain, dependency, workflow, or secret-exposure defect was found; credential-bearing preflight remains surfaced as merge risk.

Review details

Best possible solution:

Land the shared-resolver approach once maintainers explicitly accept credentialed local/private preflight scope and normal merge gates stay green.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main and v2026.6.11 send the local cron preflight GET without configured provider/request headers. The inspected LiteLLM screenshot shows the bearer-header path changing from unauthenticated 401 to authenticated 200, but I did not run a live LiteLLM setup in this read-only review.

Is this the best way to solve the issue?

Yes, with maintainer acceptance of the credential scope. Delegating to the shared provider request/header resolver is the narrowest maintainable fix for parity with normal model requests, and the latest head covers provider-default fallback and request/header preservation.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6011c9e16d21.

Label changes

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The inspected screenshot shows LiteLLM GET /models returning 401 before and 200 after for the bearer-header path.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a normal-priority cron/provider auth bug fix with bounded blast radius and real self-hosted provider impact.
  • merge-risk: 🚨 compatibility: Existing cron preflight behavior changes from unauthenticated local reachability checks to configured header-bearing requests.
  • merge-risk: 🚨 auth-provider: The diff changes provider credential lookup and Authorization/custom-header construction for cron preflight probes.
  • merge-risk: 🚨 security-boundary: The PR sends credential-bearing headers to local/private preflight endpoints, which is security-boundary-sensitive even without a confirmed leak defect.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (screenshot): The inspected screenshot shows LiteLLM GET /models returning 401 before and 200 after for the bearer-header path.
  • proof: sufficient: Contributor real behavior proof is sufficient. The inspected screenshot shows LiteLLM GET /models returning 401 before and 200 after for the bearer-header path.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The inspected screenshot shows LiteLLM GET /models returning 401 before and 200 after for the bearer-header path.
Evidence reviewed

PR surface:

Source +80, Tests +1505. Total +1585 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 85 5 +80
Tests 1 1506 1 +1505
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 1591 6 +1585

What I checked:

Likely related people:

  • vincentkoc: Recent history includes centralizing stream request headers and multiple cron/runtime touches around the shared auth/header seam this PR now uses. (role: provider auth/request config area contributor; confidence: high; commits: 331e835dab33, 28787985c4eb; files: src/agents/provider-request-config.ts, src/agents/embedded-agent-runner/model.ts, src/cron/isolated-agent/run.ts)
  • steipete: History shows substantial prior work splitting and maintaining isolated cron runner phases around the caller that invokes model preflight. (role: cron isolated runner area contributor; confidence: medium; commits: bbb73d3171e5, 41e023a80b60, 825a435709e9; files: src/cron/isolated-agent/run.ts)
  • neeravmakwana: Prior isolated cron auth profile resolution work is adjacent to this PR's agentDir/workspaceDir credential lookup wiring. (role: isolated cron auth-profile adjacent contributor; confidence: medium; commits: 12544e24d7ab; files: src/cron/isolated-agent/run.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. labels May 18, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 18, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@clawsweeper

clawsweeper Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 18, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper

clawsweeper Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@samson1357924
samson1357924 force-pushed the fix/cron-preflight-clean branch from d5f9a78 to bbacf9f Compare May 19, 2026 18:27
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 19, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 19, 2026
@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🔥 Warming up: real-behavior proof passed; findings, security review, or rank-up moves are still in progress.

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.
What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

samson1357924 added a commit to samson1357924/openclaw that referenced this pull request Jun 17, 2026
The cron job preflight check sends GET {baseUrl}/models to verify local provider connectivity, but was sending the request without an Authorization header, causing spurious 401/ECONNRESET errors.

Changes:
- Add resolveProbeApiKey() with three-source fallback chain (config.apiKey -> auth-profiles.json -> env var)
- Modify probeLocalProviderEndpoint() to accept optional apiKey and send Bearer header
- Wire apiKey resolution into preflightCronModelProvider()
- Use static fs/path imports instead of dynamic import()
- Add console.warn for auth-profiles.json read errors (no silent catch)

Tests added:
- sends Authorization Bearer header when provider config has apiKey
- does not send Authorization header when no apiKey is available

Closes openclaw#83648
samson1357924 added a commit to samson1357924/openclaw that referenced this pull request Jun 17, 2026
The cron job preflight check sends GET {baseUrl}/models to verify local provider connectivity, but was sending the request without an Authorization header, causing spurious 401/ECONNRESET errors.

Changes:
- Add resolveProbeApiKey() with three-source fallback chain (config.apiKey -> auth-profiles.json -> env var)
- Modify probeLocalProviderEndpoint() to accept optional apiKey and send Bearer header
- Wire apiKey resolution into preflightCronModelProvider()
- Use static fs/path imports instead of dynamic import()
- Add console.warn for auth-profiles.json read errors (no silent catch)

Tests added:
- sends Authorization Bearer header when provider config has apiKey
- does not send Authorization header when no apiKey is available

Closes openclaw#83648
@samson1357924
samson1357924 force-pushed the fix/cron-preflight-clean branch from ee38079 to e5180bd Compare June 17, 2026 08:13
samson1357924 added a commit to samson1357924/openclaw that referenced this pull request Jun 25, 2026
- Integrated resolveApiKeyForProvider from the shared SDK auth runtime
  for full credential chain resolution (config → auth profiles → env vars
  → plugin hooks → SecretRef)
- Added agentDir/workspaceDir params for cron agent context scoping
- Added isNonSecretApiKeyMarker guard to prevent sentinel markers
  (custom-local, ollama-local, env-var names, etc.) from being sent
  as Bearer tokens
- Added request.auth.token override tier (authorization-bearer mode)
- Added OAuth exclusion and auth leak redaction in error messages
- Guarded empty-string request.auth.token to prevent bare Bearer header
- Removed evidence screenshot from source tree (proof in PR comment)
- Expanded tests from 4 to 13 covering all auth paths + marker filtering

Closes openclaw#83648
@samson1357924

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Changes since last review:

  1. Removed evidence-preflight-fix.jpg from source tree (P2 finding addressed)
  2. Added empty-string request.auth.token guard (prevents bare Authorization: Bearer header)
  3. Fixed commit message — removed misleading "Replaced hand-rolled resolveProbeApiKey()" claim (no such function existed)
  4. Updated PR body to accurately describe the change

All 13 tests pass. Code reviewer approved. Test engineer verified coverage.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 25, 2026
@samson1357924

samson1357924 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review
PR body updated with correct Real behavior proof format (6 required fields in bold format with colon). All 13 tests pass. Evidence screenshot URL retained. Code reviewer approved. Test engineer verified coverage.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@samson1357924

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Changes since last review:

  1. Preflight now calls resolveProviderRequestHeaders unconditionally (not only when requestOverrides?.auth exists) — fixes the P1 gap where request.headers were silently dropped
  2. Fixed eslint(curly) lint failure (single-line if missing braces)
  3. PR body updated with corrected evidence and gap documentation

@clawsweeper

clawsweeper Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

The cron job preflight check sends GET {baseUrl}/models to verify local provider connectivity, but was sending the request without an Authorization header, causing spurious 401/ECONNRESET errors.

Changes:
- Add resolveProbeApiKey() with three-source fallback chain (config.apiKey -> auth-profiles.json -> env var)
- Modify probeLocalProviderEndpoint() to accept optional apiKey and send Bearer header
- Wire apiKey resolution into preflightCronModelProvider()
- Use static fs/path imports instead of dynamic import()
- Add console.warn for auth-profiles.json read errors (no silent catch)

Tests added:
- sends Authorization Bearer header when provider config has apiKey
- does not send Authorization header when no apiKey is available

Closes openclaw#83648
- Error redaction: use formatErrorMessage() to mask auth secrets in logs
- Type contract: add agentDir/workspaceDir to preflightCronModelProvider params
- Wire agentDir/workspaceDir from run.ts to preflight call
- Support request.auth.mode: 'header' for custom header auth
- Log debug-level message on resolveApiKeyForProvider failure
P1 fix: when request.auth.mode is 'header' with a valid custom auth
header, skip fall-through to resolveApiKeyForProvider() which would
inject an unwanted Authorization: Bearer header alongside the custom
header.

This matches the normal provider request path (provider-request-config.ts)
contract where header mode suppresses Bearer injection.

Test coverage:
- verify resolveApiKeyForProvider is NOT called in header mode
- verify ONLY custom header sent (no Authorization Bearer)
- verify custom Authorization header with non-Bearer prefix works
Refactor model-preflight.runtime.ts to delegate auth header construction
to the shared resolveProviderRequestHeaders/sanitizeConfiguredProviderRequest
from provider-request-config.ts, eliminating the hand-rolled auth logic.

ClawSweeper P1: custom-header auth path now reuses shared normalization.
ClawSweeper P2: cross-path auth header parity tests added.

Changes:
- Replace manual Authorization/header construction with
  resolveProviderRequestHeaders delegation
- Redact errors at cache-write time via formatErrorMessage
- Add debug log for header mode misconfiguration
- Add 4 auth parity tests (P0) + 4 edge case tests (P1)
- Remove unused apiKey/extraHeaders params from probeLocalProviderEndpoint
The preflight now calls resolveProviderRequestHeaders unconditionally,
not only when requestOverrides?.auth exists. This ensures request.headers
(configured via request.headers in provider config) are always passed
through to the shared auth header resolver, matching the normal model
request path.

Also fixes eslint(curly) in test file (single-line if must use braces).
@samson1357924

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Changes since last review:

  1. Added provider-level header parity (P1 ClawSweeper finding resolved) — sanitizeModelHeaders(providerConfig.headers) now passes as defaultHeaders to resolveProviderRequestHeaders, matching normal model path (model.ts:675)
  2. Both resolveProviderRequestHeaders calls (primary + resolveApiKeyForProvider fallback) include defaultHeaders for defense-in-depth
  3. Added 8 new provider-level header tests: basic pass-through, merge with auth, without auth, secret ref stripping, non-string filtering, undefined/empty headers boundary cases
  4. Updated resolveProviderRequestHeaders mock to merge defaultHeaders with auth headers
  5. Rebased onto latest upstream/main
    All 38 preflight tests + 30 regression tests pass.

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

- Replace headerModeConfigured (raw config check) with !requestOverrides?.auth
  (sanitized state) so misconfigured mode:header no longer blocks fallback
- Remove stale debug-log block that re-read raw config
- Update test expectations: empty headerName/value now correctly falls through
  to resolveApiKeyForProvider instead of silently dropping auth
- Add fallback+providerHeaders integration test
- Update comment to accurately describe fallback scope
@samson1357924

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Changes since last review:

  1. Fallback condition fixed: Changed from !headers && !headerModeConfigured (raw config check) to !requestOverrides?.auth (sanitized state). This fixes two bugs:
    • Provider-level headers no longer incorrectly block the auth fallback
    • Misconfigured mode:header (empty headerName/value) now correctly falls through to credential profile
  2. Deleted stale debug log block that re-read raw config — sanitizer already handles this silently
  3. New test: provider-level headers through resolveApiKeyForProvider fallback path
  4. Updated test: empty headerName/value now correctly triggers fallback (was incorrectly asserting no-fallthrough)
  5. Updated comment to accurately describe fallback scope

All 39 preflight tests + 30 regression tests pass (69/69).

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

- Spread sanitized requestOverrides into fallback resolveProviderRequestHeaders
  call so request.headers survive when env/profile credentials are used
- Update comment to reflect fixed behavior
- Add focused test: request.headers + fallback bearer auth coexisting
- Fix sanitizeConfiguredProviderRequest mock to handle request.headers
- Fix resolveProviderRequestHeaders mock to merge request.headers

ClawSweeper P1: src/cron/isolated-agent/model-preflight.runtime.ts:276
@samson1357924

samson1357924 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Changes since last review :

  1. P1 Preserve request.headers in fallback (new finding): Changed fallback resolveProviderRequestHeaders call from
    equest: { auth: { ... } }\ to
    equest: { ...requestOverrides, auth: { ... } }\ — sanitized request.headers now survive when env/profile credentials are used
  2. New test: request.headers + resolveApiKeyForProvider fallback integration test
  3. Fixed mock: sanitizeConfiguredProviderRequest mock now handles request.headers (was only checking auth)
  4. Fixed mock: resolveProviderRequestHeaders mock now merges request.headers alongside defaultHeaders and auth

All 40 preflight tests + 30 regression tests pass (70/70).

@clawsweeper

clawsweeper Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@samson1357924

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Fixed ClawSweeper P1 finding: "Treat provider-default auth as fallback auth"

Problem: When request.auth.mode is provider-default, the sanitizer returns an auth object, which makes requestOverrides?.auth truthy and blocks the resolveApiKeyForProvider fallback. The shared resolver does not inject headers for provider-default, leaving preflight unauthenticated.

Fix (model-preflight.runtime.ts):

  • Changed gate from !requestOverrides?.auth to exclude provider-default mode: hasExplicitAuth checks both truthy and mode !== "provider-default"
  • provider-default now falls through to credential lookup, matching normal model request behavior

Tests (2 new, 42 total preflight):

  1. treats provider-default auth mode as fallback-triggering no-auth
  2. preserves request.headers in fallback when provider-default mode is configured

Mock fix: Added provider-default handling to sanitizeConfiguredProviderRequestMock (was missing, returned undefined instead of matching real sanitizer output)

All 72/72 tests pass (42 preflight + 30 regression).

@clawsweeper

clawsweeper Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. size: XL stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants