Skip to content

Warn on placeholder GitHub token and expand act error extraction for auth failures (close #44)#47

Merged
wpak-ai merged 8 commits into
developfrom
fix/token-sentinel-warning
Jun 2, 2026
Merged

Warn on placeholder GitHub token and expand act error extraction for auth failures (close #44)#47
wpak-ai merged 8 commits into
developfrom
fix/token-sentinel-warning

Conversation

@bradjin8
Copy link
Copy Markdown
Collaborator

@bradjin8 bradjin8 commented Jun 2, 2026

Summary

Closes #44

Addresses the token slice of the Silent Failure Chain (T9 defaults + T10 error extraction): missing or placeholder GITHUB_TOKEN values previously produced opaque HTTP 401s from act with no upfront diagnostic.

  • Add localci.core.github_token with SENTINEL_GITHUB_TOKEN, resolution helpers, and warn_sentinel_github_token() (Rich print_warning via existing output helpers).
  • Emit a visible warning when localci run uses the sentinel, at orchestrator configuration time (dry-run plan and real execution paths), with remediation steps: export GITHUB_TOKEN, --github-token, or --offline.
  • Centralize the sentinel string in command_builder via the shared constant.
  • Expand JobExecutor._extract_error with auth-related keywords (401, unauthorized, forbidden, rate limit) in an explicit _AUTH_ERROR_EXTRACT_KEYWORDS tuple.

Test plan

  • pytest tests/test_github_token.py — token resolution, sentinel detection, warning text, Rich warning output, auth keyword registration, CLI dry-run with/without token
  • pytest tests/test_executor.py_extract_error for build errors, HTTP 401, and default secrets sentinel
  • Manual: localci run --dry-run without GITHUB_TOKEN → yellow warning with setup instructions
  • Manual: localci run --dry-run --github-token ghp_... → no sentinel warning

Summary by CodeRabbit

  • New Features

    • Emits a clear warning when no usable GitHub token is configured and falls back to a non-functional placeholder; guidance offered to provide credentials or use offline mode.
    • Important warnings are always printed even when running in quiet mode.
  • Bug Fixes

    • Improved detection of authentication failures (HTTP 401/403) and reduced false positives when parsing command output.
  • Tests

    • Added tests covering token resolution, sentinel-token warnings, auth-error extraction, and related CLI behaviors.

@bradjin8 bradjin8 self-assigned this Jun 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a sentinel-backed GitHub token resolver and warning emission, ensures those warnings bypass global quiet mode via a secondary console, expands executor error-keyword matching to include auth failures (401/403/rate-limit), integrates the resolver into CLI and command builder, and adds unit and integration tests.

Changes

GitHub Token Resolution and Error Diagnostics

Layer / File(s) Summary
GitHub Token Sentinel and Resolution Contract
cli/localci/core/github_token.py
New module defines SENTINEL_GITHUB_TOKEN and exports resolve_github_token(), is_sentinel_github_token(), format_sentinel_github_token_warning(), and warn_sentinel_github_token(); resolution precedence: CLI > env > sentinel.
Important Warning Output Infrastructure
cli/localci/utils/output.py
Adds _important_console (always non-quiet), updates configure_console() to manage both consoles, and adds print_important_warning() to ensure high-severity warnings are shown even with --quiet.
Error Keyword Expansion for Authentication
cli/localci/core/executor.py
Adds AUTH_ERROR_EXTRACT_KEYWORDS, composes _ERROR_EXTRACT_KEYWORDS to include auth/rate-limit keywords, moves re import to module scope, and centralizes per-line error checks via _line_indicates_error used by _extract_error().
Token Resolution and Warning Integration
cli/localci/cli/run.py, cli/localci/core/command_builder.py
run now imports and uses resolve_github_token() and conditionally calls warn_sentinel_github_token() when not --offline; ActCommandBuilder sets GITHUB_TOKEN to SENTINEL_GITHUB_TOKEN by default.
Comprehensive Test Coverage
cli/tests/test_github_token.py, cli/tests/test_executor.py
New and updated tests for token resolution precedence, sentinel detection, warning emission (including quiet-mode bypass), and CLI integration; executor tests extended for 401/403 extraction; updated default-secrets test to expect sentinel token.

Sequence Diagram(s)

sequenceDiagram
  participant User as CLI User
  participant CLI as localci run
  participant Resolver as resolve_github_token
  participant CommandBuilder as ActCommandBuilder
  participant WarnFn as warn_sentinel_github_token
  participant ImportantOut as _important_console

  User->>CLI: invoke run (--github-token? --offline?)
  CLI->>Resolver: pass CLI token / env lookup
  Resolver->>CLI: return resolved token
  CLI->>CommandBuilder: populate secrets (GITHUB_TOKEN = resolved token)
  CLI->>WarnFn: if not --offline, pass token
  WarnFn->>ImportantOut: print_important_warning (if sentinel)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cppalliance/local-ci-test-system#43: Integration tests for JobExecutor._extract_error() that exercise executor error extraction; related to the expanded auth-related keyword handling.

Suggested reviewers

  • wpak-ai

Poem

🐰 I found a token, small and shy,
It whispered "local-ci" and mouthed a sigh.
I shouted warnings loud and bright,
Quiet can't hide this important light,
Hop to it—set a real token tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the PR's main changes: warning on placeholder GitHub token and expanding error extraction for auth failures, with reference to issue #44.
Linked Issues check ✅ Passed All acceptance criteria from #44 are met: sentinel warning with remediation steps, expanded auth-error keywords (401, unauthorized, forbidden, rate limit), comprehensive test coverage for sentinel detection and warning behavior.
Out of Scope Changes check ✅ Passed All changes directly support issue #44: token resolution module, sentinel warning output, error keyword expansion, test coverage, and related import/output utilities. No unrelated refactoring detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/token-sentinel-warning

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cli/tests/test_executor.py (1)

612-626: ⚡ Quick win

Use tmp_path for the new executor log dirs.

These new tests hardcode /tmp/localci-test, which is POSIX-specific and can collide across parallel runs. Switching to tmp_path / "localci-test" keeps them isolated and portable.

Proposed fix
     `@patch`("shutil.which")
-    def test_extract_error_http_401(self, mock_which):
+    def test_extract_error_http_401(self, mock_which, tmp_path):
         mock_which.return_value = "/usr/bin/act"
-        executor = JobExecutor(logs_dir=Path("/tmp/localci-test"))
+        executor = JobExecutor(logs_dir=tmp_path / "localci-test")
         output = "fetching action\nHTTP 401: Bad credentials\nend"
         extracted = executor._extract_error(output)
         assert "401" in extracted
 
     `@patch`("shutil.which")
-    def test_extract_error_http_403(self, mock_which):
+    def test_extract_error_http_403(self, mock_which, tmp_path):
         mock_which.return_value = "/usr/bin/act"
-        executor = JobExecutor(logs_dir=Path("/tmp/localci-test"))
+        executor = JobExecutor(logs_dir=tmp_path / "localci-test")
         output = "fetching action\nreceived HTTP status: 403\nend"
         extracted = executor._extract_error(output)
         assert "403" in extracted
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/tests/test_executor.py` around lines 612 - 626, The tests use a hardcoded
POSIX path for logs_dir; update both test_extract_error_http_401 and
test_extract_error_http_403 to accept the pytest tmp_path fixture and construct
the logs_dir from tmp_path (e.g. logs_dir=tmp_path / "localci-test") instead of
Path("/tmp/localci-test") so they are portable and isolated; keep the existing
JobExecutor(...) call but replace the Path(...) argument and add tmp_path as a
function parameter to each test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/localci/cli/run.py`:
- Around line 160-161: The sentinel warning is shown even in offline mode; only
call warn_sentinel_github_token when offline is false. After retrieving gh_token
via resolve_github_token(github_token), wrap the
warn_sentinel_github_token(gh_token) call in a conditional check (e.g., if not
offline:) so that warn_sentinel_github_token is skipped when the offline flag is
enabled.

---

Nitpick comments:
In `@cli/tests/test_executor.py`:
- Around line 612-626: The tests use a hardcoded POSIX path for logs_dir; update
both test_extract_error_http_401 and test_extract_error_http_403 to accept the
pytest tmp_path fixture and construct the logs_dir from tmp_path (e.g.
logs_dir=tmp_path / "localci-test") instead of Path("/tmp/localci-test") so they
are portable and isolated; keep the existing JobExecutor(...) call but replace
the Path(...) argument and add tmp_path as a function parameter to each test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56a09f5c-84cd-48f5-9520-102fa06877cb

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca159b and a64d956.

📒 Files selected for processing (7)
  • cli/localci/cli/run.py
  • cli/localci/core/command_builder.py
  • cli/localci/core/executor.py
  • cli/localci/core/github_token.py
  • cli/localci/utils/output.py
  • cli/tests/test_executor.py
  • cli/tests/test_github_token.py

Comment thread cli/localci/cli/run.py Outdated
@bradjin8
Copy link
Copy Markdown
Collaborator Author

bradjin8 commented Jun 2, 2026

@coderabbitai review this PR fully.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== github_token.py ==="
cat cli/localci/core/github_token.py

echo ""
echo "=== run.py ==="
cat cli/localci/cli/run.py

echo ""
echo "=== command_builder.py ==="
cat cli/localci/core/command_builder.py

echo ""
echo "=== executor.py ==="
cat cli/localci/core/executor.py

echo ""
echo "=== output.py ==="
cat cli/localci/utils/output.py

echo ""
echo "=== test_github_token.py ==="
cat cli/tests/test_github_token.py

echo ""
echo "=== test_executor.py ==="
cat cli/tests/test_executor.py

echo ""
echo "=== Directory structure ==="
tree cli/localci/core/ cli/localci/cli/ cli/localci/utils/ 2>/dev/null || find cli/localci -name "*.py" | sort

Length of output: 114208



@bradjin8 Here is a full review of PR #47.


Overall Assessment

The PR cleanly addresses issue #44. The github_token.py module is well-structured, the _important_console pattern for bypassing --quiet is elegant, and the test suite is thorough. A few issues are worth addressing before merge.


Issues

1. run.pyimport re out of order (PEP 8)

import re appears after the third-party import click, breaking the standard-library → third-party → local import ordering required by PEP 8.

# Current (wrong order)
import click
import re   # ← stdlib import placed after third-party

# Fix
import os
import re          # ← move here, with other stdlib imports
import tempfile
from pathlib import Path

import click

2. executor.py"401" substring can produce false positives

The keyword "401" matched case-insensitively as a plain substring will fire on unrelated output like "fetched 4010 bytes", "port 40100", or "HTTP/2 4012". A tighter pattern prevents noise.

# Tighter alternative using a word-boundary-aware regex helper,
# or at minimum matching only HTTP status patterns:
AUTH_ERROR_EXTRACT_KEYWORDS = (
    " 401 ",    # space-padded to reduce false positives
    " 403 ",
    "401:",
    "403:",
    "unauthorized",
    "forbidden",
    "rate limit",
)

Or, since _extract_error already iterates lines, a small regex check for \b4[0-9]{2}\b would be cleaner. The current approach is acceptable as a heuristic but worth tightening.


3. github_token.py — internal ticket references in the module docstring

The docstring references (T9 defaults + T10 error extraction) which are opaque to external contributors and will rot as the project evolves.

# Current
"""GitHub token resolution for act and workflow action downloads.

Part of the token slice of the Silent Failure Chain (T9 defaults + T10 error
extraction): missing tokens previously produced opaque act 401s with no
upfront warning.
"""

# Suggested
"""GitHub token resolution for act and workflow action downloads.

When no GitHub token is provided, act falls back to a non-functional
placeholder, which produces opaque HTTP 401 errors during action downloads.
This module surfaces that condition as an early, visible warning.
"""

4. test_github_token.pycapsys + Rich console fragility

_important_console is a module-level Console() object constructed at import time, before pytest's capsys patches sys.stdout. Rich's Console stores the file reference at construction time (it does not lazily re-read sys.stdout). This means capsys may not capture the output — the test could silently pass only because pytest also captures at the file-descriptor level (fd-level capture, pytest's default).

The reliable fix is to pass file=sys.stdout explicitly so Rich re-resolves the stream at print time, or to use CliRunner(mix_stderr=False) with capsys only in integration tests.

# In output.py — make the console use sys.stdout lazily
import sys

def print_important_warning(message: str) -> None:
    """Print a warning that is still shown when ``--quiet`` is set."""
    _important_console.print(f"[warning]![/warning] {message}", file=sys.stdout)

Or, for the unit tests, use CliRunner consistently (as the integration tests already do) rather than capsys, since CliRunner properly isolates stdout.


5. output.py_important_console initial state inconsistency (minor)

The module-level initial consoles are:

console = Console(theme=LOCALCI_THEME)                 # quiet=False (default), no_color auto
_important_console = Console(theme=LOCALCI_THEME)      # same

This is fine in practice since configure_console is called early by the CLI entry point. However, if print_important_warning is called before configure_console (e.g., in library usage), no_color won't be respected. Consider defaulting both to safe values:

console = Console(theme=LOCALCI_THEME, no_color=False)
_important_console = Console(theme=LOCALCI_THEME, no_color=False, quiet=False)

Nits

  • AUTH_ERROR_EXTRACT_KEYWORDS (public, no underscore) vs _ERROR_EXTRACT_KEYWORDS (private) — the inconsistency is intentional for test imports, but a brief comment on why it's public would help future readers.
  • command_builder.py: secrets.setdefault("GITHUB_TOKEN", SENTINEL_GITHUB_TOKEN) is correct — user-provided secrets win — but a short comment explaining the intent (sentinel is last resort, not override) would aid maintainability.

Positive Highlights

  • The warning fires before workflow parsing, so users see it even when no jobs match — exactly the right place.
  • --offline correctly suppresses the warning. The logic if not offline: warn_sentinel_github_token(gh_token) is clean.
  • print_important_warning bypassing --quiet via a separate Console(quiet=False) is the right design.
  • Token resolution precedence (CLI → env → sentinel) with whitespace stripping is robust.
  • Test coverage is comprehensive, including the configure_console(quiet=True) case and the "no jobs match" early exit.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/tests/test_executor.py`:
- Around line 628-635: The test test_extract_error_ignores_401_false_positive
currently can pass for both correct and incorrect implementations; change the
mocked output and assertions so error-triggered extraction would yield a
different result than the fallback: feed _extract_error (on JobExecutor) an
output with at least 3 lines where the "progress: fetched 4010 bytes from cache"
line is not in the last two lines (e.g., put a trailing "all done" plus another
line after it), keep max_lines=2, then assert that the extracted text does not
contain "4010" (to prove the "401" substring did not trigger extraction) and
that it contains the expected fallback last-line content (e.g., "all done" or
the final line) to confirm fallback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37164f43-4bc4-4710-9132-2043ad2e428e

📥 Commits

Reviewing files that changed from the base of the PR and between bc4cdde and 907edd8.

📒 Files selected for processing (7)
  • cli/localci/cli/run.py
  • cli/localci/core/command_builder.py
  • cli/localci/core/executor.py
  • cli/localci/core/github_token.py
  • cli/localci/utils/output.py
  • cli/tests/test_executor.py
  • cli/tests/test_github_token.py
💤 Files with no reviewable changes (1)
  • cli/tests/test_github_token.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • cli/localci/core/github_token.py
  • cli/localci/cli/run.py
  • cli/localci/utils/output.py
  • cli/localci/core/executor.py
  • cli/localci/core/command_builder.py

Comment thread cli/tests/test_executor.py Outdated
@bradjin8 bradjin8 requested a review from henry0816191 June 2, 2026 10:48
Copy link
Copy Markdown
Collaborator

@henry0816191 henry0816191 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approved this PR, but left a comment

Comment thread cli/tests/test_executor.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/tests/test_executor.py`:
- Around line 617-631: Update the two tests for JobExecutor._extract_error to
ensure they exercise the auth-extraction branch: craft longer multi-line output
so the fallback path cannot trivially capture the 401/403, and include one of
the literal auth keywords from AUTH_ERROR_EXTRACT_KEYWORDS (e.g.,
"unauthorized", "forbidden", or "rate limit") alongside the HTTP status line;
then assert that the returned extracted string contains both the numeric status
("401"/"403") and the auth keyword to verify the auth-specific extraction logic
in _extract_error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27d15f32-10f4-4b13-b30e-dddf7ac9688f

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf2f50 and c954792.

📒 Files selected for processing (1)
  • cli/tests/test_executor.py

Comment thread cli/tests/test_executor.py
@bradjin8 bradjin8 requested a review from wpak-ai June 2, 2026 15:56
@wpak-ai wpak-ai merged commit 84d33f0 into develop Jun 2, 2026
5 checks passed
@wpak-ai wpak-ai deleted the fix/token-sentinel-warning branch June 2, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Token sentinel warning + error keyword expansion

3 participants