Skip to content

feat: enforce debug/host guard and document API versioning#60

Merged
wpak-ai merged 6 commits into
masterfrom
feat/api-versioning-debug-guard
Jun 2, 2026
Merged

feat: enforce debug/host guard and document API versioning#60
wpak-ai merged 6 commits into
masterfrom
feat/api-versioning-debug-guard

Conversation

@clean6378-max-it
Copy link
Copy Markdown
Collaborator

@clean6378-max-it clean6378-max-it commented Jun 2, 2026

Closes #58

Summary

Single PR for Monday API policy and security work on claude-code-chat-browser:

  • Security: Startup guard blocks --debug with non-loopback --host (e.g. 0.0.0.0). Loopback + debug and 0.0.0.0 without debug remain allowed.
  • Release hygiene: __version__ (0.1.0), CHANGELOG.md, and docs/deprecation-policy.md linked from README and CONTRIBUTING.
  • API contract: Field stability (stable / experimental / deprecated) documented in docs/api-reference.md for export state, project list, session list, and session detail. export_count is deprecated in docs only — still returned in JSON (removal is Thursday).

Sprint items

Changes

Area Files
Guard app.py (is_loopback_host, validate_startup_cli), tests/test_cli_args.py
Versioning CHANGELOG.md, docs/deprecation-policy.md, app.py (__version__)
Docs docs/api-reference.md, README.md, CONTRIBUTING.md

Test plan

  • python -m pytest -q — full suite green
  • python -m mypy
  • python -m pytest tests/test_cli_args.py::TestAppArgparse -v --no-cov — 26 passed
  • Manual: python app.py --host 0.0.0.0 --debug → stderr error, exit 1
  • Manual: python app.py --host 127.0.0.1 --debug → starts, debug on
  • Manual: python app.py --host 0.0.0.0 (no --debug) → starts, debug off
  • npm test (no JS changes; optional)

Out of scope

  • Removing export_count from responses (Thursday #4)
  • sessions.js dispatch registry (Tuesday #1)
  • Windows CI matrix (Wednesday #2)

Summary by CodeRabbit

  • New Features

    • Recorded development version 0.1.0.dev0 in the project metadata; changelog added.
  • Changed

    • Server startup now blocks debug mode when bound to non-loopback hosts; listen URL display improved.
  • Documentation

    • Added API field stability guidance and a deprecation policy; updated README and CONTRIBUTING with startup guidance.
  • Deprecations

    • export_count in GET /api/export/state deprecated; use last_export_session_count.
  • Tests

    • Added CLI/startup and listen-URL formatting tests for loopback enforcement.

@clean6378-max-it clean6378-max-it 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

Enforces a startup guard refusing Flask --debug when --host is non-loopback, adds module version (0.1.0.dev0), creates CHANGELOG and deprecation-policy docs, annotates API reference fields as stable/experimental/deprecated, and adds CLI/tests for loopback validation and URL formatting.

Changes

Version, security guard, deprecation policy, and API stability

Layer / File(s) Summary
Startup CLI guard: loopback-host validation and tests
app.py, tests/test_cli_args.py
Implements _normalize_bind_host, is_loopback_host(), format_listen_url(), and validate_startup_cli() wired into __main__; rejects --debug with non-loopback --host (prints error and exits 1). Tests cover loopback acceptance, non-loopback rejection, and URL formatting.
Release version, CHANGELOG, and deprecation policy
CHANGELOG.md, docs/deprecation-policy.md, app.py
Adds __version__ = "0.1.0.dev0", creates CHANGELOG.md (Unreleased) and docs/deprecation-policy.md defining stability tiers, deprecate-before-remove windows, and announcement/removal criteria.
API response field stability annotations
docs/api-reference.md
Adds "API field stability" section and updates response schema tables for multiple endpoints to include a Stability column; marks export_count as deprecated and documents last_export_session_count as the preferred field.
Contributing guide and README updates
CONTRIBUTING.md, README.md
Updates dev-server flag guidance (trusted --host 0.0.0.0 only, not with --debug), requires syncing API stability and CHANGELOG before removing JSON fields, and clarifies the startup refusal applies to python app.py invocations.

Sequence Diagram: startup validation flow

sequenceDiagram
  participant CLI
  participant ArgParse
  participant ValidateStartupCLI
  participant IsLoopbackHost
  participant FlaskApp

  CLI->>ArgParse: parse args (--debug, --host)
  ArgParse-->>ValidateStartupCLI: args
  ValidateStartupCLI->>IsLoopbackHost: normalize host / check loopback
  IsLoopbackHost-->>ValidateStartupCLI: True/False
  alt non-loopback + debug
    ValidateStartupCLI->>CLI: write error to stderr & SystemExit(1)
  else loopback or no debug
    ValidateStartupCLI-->>FlaskApp: proceed to run()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • timon0305
  • wpak-ai

Poem

🐰 A version is born, a policy penned,
Debug doors closed where networks extend,
Fields labeled steady, new, or deprecated,
Tests hop along to ensure rules are gated,
Hooray — changelogs and safety now attended!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% 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 PR title 'feat: enforce debug/host guard and document API versioning' accurately summarizes the main security and documentation changes.
Linked Issues check ✅ Passed All three coding requirements from issue #58 are met: debug/host guard with tests, version and CHANGELOG.md with deprecation policy, and API stability documentation for required endpoints.
Out of Scope Changes check ✅ Passed All changes align with the three focused areas in issue #58; out-of-scope items like export_count removal and Windows CI are correctly excluded.

✏️ 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 feat/api-versioning-debug-guard

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 (2)
tests/test_cli_args.py (1)

337-342: ⚡ Quick win

Assert the stderr contract on the rejection path.

This only checks the exit code, so the “clear stderr message” part of the guard can regress unnoticed. Capture stderr here and assert a stable substring from the error text.

Suggested test update
-    def test_validate_startup_cli_rejects_non_loopback_debug(self) -> None:
+    def test_validate_startup_cli_rejects_non_loopback_debug(
+        self, capsys: pytest.CaptureFixture[str]
+    ) -> None:
         parser = build_cli_parser()
         args = parser.parse_args(["--host", "0.0.0.0", "--debug"])
         with pytest.raises(SystemExit) as exc_info:
             validate_startup_cli(args)
         assert exc_info.value.code == 1
+        assert "--debug is only allowed with a loopback --host" in capsys.readouterr().err
🤖 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 `@tests/test_cli_args.py` around lines 337 - 342, Update the test to also
capture stderr and assert a stable substring from the error output: add the
pytest capsys fixture to test_validate_startup_cli_rejects_non_loopback_debug,
call validate_startup_cli via the existing pytest.raises(SystemExit) context,
then after the context use capsys.readouterr().err and assert it contains a
stable substring indicating the rejection (e.g. keywords like "debug" and
"loopback" or a short phrase your validate_startup_cli emits) to ensure the
clear stderr message is verified.
docs/api-reference.md (1)

247-261: ⚡ Quick win

Consider adding Stability column to stats endpoint for consistency.

The stats endpoint table lacks a Stability column while GET /api/projects, GET /api/projects/.../sessions, GET /api/sessions/.../, and GET /api/export/state all include stability annotations. Adding a Stability column here would provide consistent documentation style and help future-proof this endpoint against unintentional breaking changes.

🤖 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 `@docs/api-reference.md` around lines 247 - 261, Add a "Stability" column to
the stats endpoint table in docs/api-reference.md: update the header row to
include "Stability" and add stability annotations (e.g., "stable",
"experimental", "deprecated") for each listed field such as files_touched,
commands_run, urls_accessed, conversation_turns, wall_clock_seconds,
wall_clock_display, cost_estimate_usd, tool_result_summary, stop_reason_summary,
entry_type_counts, sidechain_message_count, api_error_count, and
compaction_events so the table format matches the other endpoints (GET
/api/projects, GET /api/projects/.../sessions, GET /api/sessions/.../, GET
/api/export/state).
🤖 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 `@app.py`:
- Around line 18-29: The startup banner prints the host verbatim (so IPv6 like
::1 becomes an invalid URL such as http://::1:5000); when formatting the
displayed address (where the app constructs the startup URL), detect IPv6
literals (e.g. host contains ':' or use is_loopback_host/hostname variable) and
wrap the host in square brackets before appending the port (produce
http://[::1]:5000), ensuring you don't double-bracket already-bracketed hosts;
alternatively, if you prefer, avoid rendering a clickable URL when the host
contains a colon.

---

Nitpick comments:
In `@docs/api-reference.md`:
- Around line 247-261: Add a "Stability" column to the stats endpoint table in
docs/api-reference.md: update the header row to include "Stability" and add
stability annotations (e.g., "stable", "experimental", "deprecated") for each
listed field such as files_touched, commands_run, urls_accessed,
conversation_turns, wall_clock_seconds, wall_clock_display, cost_estimate_usd,
tool_result_summary, stop_reason_summary, entry_type_counts,
sidechain_message_count, api_error_count, and compaction_events so the table
format matches the other endpoints (GET /api/projects, GET
/api/projects/.../sessions, GET /api/sessions/.../, GET /api/export/state).

In `@tests/test_cli_args.py`:
- Around line 337-342: Update the test to also capture stderr and assert a
stable substring from the error output: add the pytest capsys fixture to
test_validate_startup_cli_rejects_non_loopback_debug, call validate_startup_cli
via the existing pytest.raises(SystemExit) context, then after the context use
capsys.readouterr().err and assert it contains a stable substring indicating the
rejection (e.g. keywords like "debug" and "loopback" or a short phrase your
validate_startup_cli emits) to ensure the clear stderr message is verified.
🪄 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: 86ed3edd-167a-4f72-8895-7e62b9cd49f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2a4c6 and eb08e28.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • app.py
  • docs/api-reference.md
  • docs/deprecation-policy.md
  • tests/test_cli_args.py

Comment thread app.py
@clean6378-max-it
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, please review this PR fully

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== app.py ==="
cat app.py

echo ""
echo "=== tests/test_cli_args.py ==="
cat tests/test_cli_args.py

echo ""
echo "=== CHANGELOG.md ==="
cat CHANGELOG.md

echo ""
echo "=== CONTRIBUTING.md ==="
cat CONTRIBUTING.md

echo ""
echo "=== README.md ==="
cat README.md

echo ""
echo "=== docs/api-reference.md ==="
cat docs/api-reference.md

echo ""
echo "=== docs/deprecation-policy.md ==="
cat docs/deprecation-policy.md

Length of output: 51063



@clean6378-max-it: Here is the full review of PR #60.


Summary

The PR lands three well-scoped changes — a debug/host startup guard, versioning + CHANGELOG + deprecation policy, and API stability documentation. The overall shape is solid. A handful of issues worth addressing are listed below.


app.py

1. Dead branch in is_loopback_hostlen(parts) == 4 is always True

When control reaches the second if, the preconditions already guarantee exactly four parts:

if h.startswith("127.") and h.count(".") == 3:
    parts = h.split(".")
    try:
        return len(parts) == 4 and ...   # ← always True here; dead check

h.startswith("127.") accounts for one dot and h.count(".") == 3 ensures three dots total, so h.split(".") is always a 4-element list. The guard can be simplified:

if h.startswith("127.") and h.count(".") == 3:
    parts = h.split(".")
    try:
        return all(0 <= int(p) <= 255 for p in parts)
    except ValueError:
        return False

2. format_listen_url("") returns "http://:5000" — no validation

If host is an empty string (e.g. from a programmatic caller), display_host stays "" and the banner is http://:5000. Consider a guard or assertion:

def format_listen_url(host: str, port: int) -> str:
    h = (host or "").strip()
    if not h:
        raise ValueError("host must not be empty")
    ...

3. validate_startup_cli — consider using sys.exit alias for clarity

Minor: raise SystemExit(1) works perfectly, but sys.exit(1) reads more naturally alongside print(..., file=sys.stderr). No functional difference.


tests/test_cli_args.py

4. Missing edge-case coverage for the 127.x.x.x branch in is_loopback_host

The ValueError path (line except ValueError: return False) is only reachable with a malformed 127.* address, but it is never exercised. Adding a few cases ensures the full branch is covered:

`@pytest.mark.parametrize`("host", [
    "0.0.0.0", "192.168.1.1", "", "example.com",
    "127.0.0.",      # trailing dot → empty last octet, ValueError
    "127.256.0.0",   # octet out of range
    "127.-1.0.0",    # negative octet
])
def test_is_loopback_host_rejects_non_loopback(self, host: str) -> None:
    assert not is_loopback_host(host)

docs/api-reference.md

5. GET /api/search response table is missing the Stability column

Every other endpoint table in this file has a Stability column (stable / experimental / deprecated). The search hit fields (project, session_id, title, role, timestamp, snippet) are absent from that contract:

Field Type Stability Description
project string stable
session_id string stable
title string stable
role string stable
timestamp string | null stable
snippet string experimental ~160 chars around match; length may change

CHANGELOG.md

6. Missing footer reference link (Keep a Changelog convention)

Keep a Changelog expects each version heading [0.1.0] to be a link resolved at the file footer:

[0.1.0]: https://github.com/cppalliance/claude-code-chat-browser/releases/tag/v0.1.0

or a GitHub comparison link. Without it, [0.1.0] renders as plain text, not a hyperlink.


docs/deprecation-policy.md

7. "At least one release" notice window is very short

This is listed as a principle choice (pre-1.0 project), so it is intentional, but consider adding a note that the window may extend for fields used by the bundled SPA (which tracks no external versioning). For example:

For fields actively read by the bundled SPA, the deprecation period will span at least two releases to allow the SPA and policy to be updated atomically.

Not a blocker — just a suggestion for integrators reading the policy.


Minor Nits

  • README.md Options code block (python app.py --port 8080 --host 0.0.0.0) appears before the security warning box; a short inline comment # never add --debug here would be a helpful signal even before the reader reaches the warning.
  • CONTRIBUTING.md is clean and well updated.

Verdict

The security guard logic and tests are correct. The three areas most worth fixing before merge:

  1. The redundant len(parts) == 4 check (low effort, avoids confusion for future readers).
  2. The missing Stability column on the search endpoint table (consistency with the rest of the doc).
  3. The CHANGELOG footer reference link (convention compliance).

- Simplify 127.x.x.x loopback check; reject empty host in format_listen_url
- Add malformed 127.* tests; search endpoint stability column
- CHANGELOG [0.1.0] footer link; SPA two-release deprecation note
- README inline warning on 0.0.0.0 example
Comment thread app.py
Comment thread CHANGELOG.md Outdated
Comment thread docs/deprecation-policy.md
Comment thread docs/api-reference.md Outdated
Comment thread app.py
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CHANGELOG.md (1)

7-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add footer comparison link for [Unreleased] per Keep a Changelog convention.

The changelog follows the Keep a Changelog format but is missing the footer reference link. Add a comparison link pointing to the unreleased commits (typically comparing the latest tag or initial commit to HEAD).

📎 Suggested footer addition

Add this section at the end of the file (adjust the repository URL and comparison base as appropriate):

 - `export_count` on `GET /api/export/state` (documented only; still returned). Use `last_export_session_count`. Removal planned in a follow-up release per [deprecation policy](docs/deprecation-policy.md).
+
+[Unreleased]: https://github.com/owner/repo/compare/v0.0.0...HEAD

If no prior release exists, compare against the initial commit or use /commits/master instead of a comparison URL.

🤖 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 `@CHANGELOG.md` around lines 7 - 22, Add the missing Keep a Changelog footer
link for the [Unreleased] section by appending a comparison reference at the end
of CHANGELOG.md that points from the previous release tag (or the initial
commit/master if no prior tag) to HEAD; ensure the link label matches
"[Unreleased]" and update the comparison URL to your repository and chosen base
(e.g., v0.1.0..HEAD or initial-commit..HEAD) so the Unreleased header resolves
to that diff URL.
🤖 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.

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 7-22: Add the missing Keep a Changelog footer link for the
[Unreleased] section by appending a comparison reference at the end of
CHANGELOG.md that points from the previous release tag (or the initial
commit/master if no prior tag) to HEAD; ensure the link label matches
"[Unreleased]" and update the comparison URL to your repository and chosen base
(e.g., v0.1.0..HEAD or initial-commit..HEAD) so the Unreleased header resolves
to that diff URL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 608705d8-fae1-4f60-b0c5-7ab36a582d08

📥 Commits

Reviewing files that changed from the base of the PR and between ab1bb2e and 1f5456e.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • CONTRIBUTING.md
  • app.py
  • docs/api-reference.md
  • docs/deprecation-policy.md
  • tests/test_cli_args.py
✅ Files skipped from review due to trivial changes (1)
  • CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/deprecation-policy.md
  • app.py
  • tests/test_cli_args.py
  • docs/api-reference.md

@clean6378-max-it
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, Can you check @timon0305 's comments are addressed correctly?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== PR `#60` Review Comments by timon0305 ==="
gh api repos/cppalliance/claude-code-chat-browser/pulls/60/comments --jq '.[] | select(.user.login == "timon0305") | {id: .id, path: .path, line: .line, body: .body, position: .position}'

echo ""
echo "=== PR `#60` Issue Comments by timon0305 ==="
gh api repos/cppalliance/claude-code-chat-browser/issues/60/comments --jq '.[] | select(.user.login == "timon0305") | {id: .id, body: .body}'

echo ""
echo "=== All PR reviews by timon0305 ==="
gh api repos/cppalliance/claude-code-chat-browser/pulls/60/reviews --jq '.[] | select(.user.login == "timon0305") | {id: .id, state: .state, body: .body}'

Length of output: 3347


🏁 Script executed:

#!/bin/bash
echo "=== app.py (is_loopback_host and related functions) ==="
cat app.py

echo ""
echo "=== CHANGELOG.md ==="
cat CHANGELOG.md

echo ""
echo "=== docs/deprecation-policy.md ==="
cat docs/deprecation-policy.md

echo ""
echo "=== docs/api-reference.md ==="
cat docs/api-reference.md

Length of output: 23874



@clean6378-max-it: Here is the status of all four substantive comments from @timon0305:


Comment 1 — is_loopback_host rejecting [::1] (bracketed IPv6) ✅ Addressed

The new _normalize_bind_host() helper strips brackets before the loopback check, so [::1] is normalized to ::1 and accepted. The docstring explicitly lists [::1] as a valid input.


Comment 2 — CHANGELOG declaring 0.1.0 before a git tag exists ✅ Addressed

Both of timon0305's suggested options were applied:

  • __version__ = "0.1.0.dev0" (option c) in app.py
  • ## [Unreleased] heading in CHANGELOG.md (option b), with a proper [Unreleased] comparison link at the footer

Comment 3 — Vague deprecation window and missing major-bump explanation ✅ Addressed

docs/deprecation-policy.md now specifies:

  • A concrete version-bound rule with an example: "deprecated in 0.1.0, removable from 0.3.0 at earliest"
  • A time-bound floor: "no earlier than 14 calendar days after the deprecation announcement"
  • A Versioning table with an explicit row for Major: "Reserved for the 1.0.0 line and later: signals a stable HTTP JSON contract…"

Comment 4 — Misleading "Field stability:" label in docs/api-reference.md ✅ Addressed

The header now reads exactly what timon0305 suggested:

"Deprecation and removal: See deprecation-policy.md for how stability labels are applied and fields are removed. Field labels are defined in API field stability below."


Comment 5 — Guard not covering flask run ⚠️ Acknowledged as out-of-scope

validate_startup_cli is still only called from __main__. timon0305 explicitly marked this as non-blocking and out-of-scope for this PR, and approved the PR with that understanding. The README and CONTRIBUTING already document the limitation. This would be a good follow-up issue if you want to track it.


Overall: All four blocking/substantive comments from @timon0305 are properly addressed. The PR has been approved by @timon0305.

@clean6378-max-it clean6378-max-it requested a review from wpak-ai June 2, 2026 17:39
@wpak-ai wpak-ai merged commit 7944ddb into master Jun 2, 2026
6 checks passed
@wpak-ai wpak-ai deleted the feat/api-versioning-debug-guard branch June 2, 2026 18:23
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.

claude-code-chat-browser: Enforce debug/host guard, versioning policy, and API response stability docs

3 participants