Skip to content

[codex] surface refresh auth and /data permission failures#39

Merged
zachyzissou merged 2 commits intomainfrom
codex/refresh-auth-permission-surfacing
Feb 25, 2026
Merged

[codex] surface refresh auth and /data permission failures#39
zachyzissou merged 2 commits intomainfrom
codex/refresh-auth-permission-surfacing

Conversation

@zachyzissou
Copy link
Owner

Summary

  • make refresh failures explicit in WebUI:
    • parse API error details
    • show clear unauthorized guidance when /refresh returns 401
    • poll /status after refresh trigger so the UI reflects async generation instead of appearing as a no-op
    • display configured refresh_access_mode in quick stats
  • improve health diagnostics for deployment issues:
    • add checks.data_dir_writable
    • include refresh_access_mode in /health and /status
    • add HEAD /health endpoint for probe compatibility
  • add integration coverage for refresh_access_mode, data_dir_writable, and HEAD /health
  • update troubleshooting docs for unauthorized refresh and /data permission-denied scenarios

Validation

  • python3 -m black app/server.py test_integration.py
  • python3 -m py_compile app/server.py test_integration.py
  • npm run -s lint:js
  • npm run -s lint:md

Notes

  • local runtime tests were not executed here because this environment has Python 3.9 while the project targets 3.11+.

Copilot AI review requested due to automatic review settings February 25, 2026 05:27
@zachyzissou zachyzissou merged commit 8c98c15 into main Feb 25, 2026
13 checks passed
@zachyzissou zachyzissou deleted the codex/refresh-auth-permission-surfacing branch February 25, 2026 05:30
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves operator and user-facing diagnostics around refresh authorization failures and /data permission issues, while making the WebUI reflect asynchronous refresh behavior more clearly via /status polling.

Changes:

  • WebUI: poll /status after triggering /refresh, parse API error details, and display refresh_access_mode in quick stats.
  • API/ops: add checks.data_dir_writable, include refresh_access_mode in /health and /status, and add HEAD /health for probe compatibility.
  • Tests/docs: extend integration assertions for the new fields and HEAD endpoint; add troubleshooting guidance for unauthorized refresh and /data permission errors.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
web/assets/app.js Adds refresh-status polling, better refresh error messaging, and displays refresh_access_mode in the UI.
app/server.py Adds refresh_access_mode helper, checks.data_dir_writable, and HEAD /health.
test_integration.py Adds assertions for refresh_access_mode, data_dir_writable, and HEAD /health.
README.md Updates troubleshooting guidance for refresh auth and /data permission failures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1030 to 1041
data_dir_exists = DATA_DIR.exists()
data_dir_writable = data_dir_exists and os.access(DATA_DIR, os.W_OK)

# Check basic functionality
health_status = {
"status": "healthy",
"timestamp": datetime.now(UTC).isoformat(),
"checks": {
"api": "ok",
"data_dir": "ok" if DATA_DIR.exists() else "error",
"data_dir": "ok" if data_dir_exists else "error",
"data_dir_writable": "ok" if data_dir_writable else "error",
"web_assets": "ok" if (WEB_DIR / "assets").exists() else "warning",
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

data_dir_writable uses os.access(DATA_DIR, os.W_OK) which isn’t sufficient for directories (you typically need execute permission as well to create files, and os.access can still be a false-positive depending on filesystem semantics). To avoid reporting writable when the app can’t actually write, check for both W_OK | X_OK at minimum, or perform a lightweight create/delete probe in DATA_DIR (and treat failures as not writable).

Copilot uses AI. Check for mistakes.
Comment on lines 383 to +386
const btn = event.currentTarget;
const originalText = btn.textContent;
const beforeStatus = await fetchStatusSnapshot();
const previousLastUpdate = beforeStatus?.last_update ?? null;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The refresh click handler captures originalText = btn.textContent and later restores it via btn.innerHTML = originalText. This drops the original <span aria-hidden="true">↻</span> markup from index.html and can make the icon text announced by screen readers (accessibility regression). Store/restore btn.innerHTML (or rebuild the original DOM structure) instead of using textContent for the reset path.

Copilot uses AI. Check for mistakes.
Comment on lines 382 to 387
byId('refresh').addEventListener('click', async event => {
const btn = event.currentTarget;
const originalText = btn.textContent;
const beforeStatus = await fetchStatusSnapshot();
const previousLastUpdate = beforeStatus?.last_update ?? null;

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

fetchStatusSnapshot() is awaited before the button is disabled/loading state is set. If /status is slow/hung, the user can click multiple times and trigger multiple concurrent refresh handlers before the UI locks, potentially causing multiple /refresh calls. Consider disabling the button / setting aria-busy before awaiting the status snapshot (or reusing the last loaded status).

Copilot uses AI. Check for mistakes.
Comment on lines +340 to +346
def _refresh_access_mode() -> str:
"""Return configured refresh access mode for diagnostics/UI."""
if ALLOW_ANONYMOUS_LOCAL_REFRESH:
return "lan_anonymous"
if APP_REFRESH_TOKEN:
return "token_required"
return "disabled"
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

_refresh_access_mode() can return an inaccurate mode when both ALLOW_ANONYMOUS_LOCAL_REFRESH and APP_REFRESH_TOKEN are set. In that configuration, /refresh is allowed from LAN without auth and allowed elsewhere with the token, but this function reports only lan_anonymous. Consider returning a distinct value for the combined case (or exposing both flags separately) so /status, /health, and the WebUI don’t mislead operators.

Copilot uses AI. Check for mistakes.
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.

2 participants