RepoPilot

tornadoweb/tornado

Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.

Healthy

Healthy across the board

HealthyDependency

Permissive license, no critical CVEs, actively maintained — safe to depend on.

HealthyFork & modify

Has a license, tests, and CI — clean foundation to fork and modify.

HealthyLearn from

Documented and popular — useful reference codebase to read through.

HealthyDeploy as-is

No critical CVEs, sane security posture — runnable as-is.

  • Concentrated ownership — top contributor handles 78% of recent commits
  • Last commit 2d ago
  • 11 active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present

Computed from maintenance signals — commit recency, contributor breadth, bus factor, license, CI, tests, cross-checked against OpenSSF Scorecard

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Want this for your own repo?

Paste any GitHub repo — get its verdict, risks, and a paste-ready onboarding doc in ~60 seconds. Free, no sign-up.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/tornadoweb/tornado)](https://repopilot.app/r/tornadoweb/tornado)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card

This card auto-renders when someone shares https://repopilot.app/r/tornadoweb/tornado on X, Slack, or LinkedIn.

Ask AI about tornadoweb/tornado

Grounded in the actual source code. Pick a starter question or write your own.

Or write your own question →

Onboarding doc

Onboarding: tornadoweb/tornado

Generated by RepoPilot · 2026-06-27 · Source

🎯Verdict

GO — Healthy across the board

  • Last commit 2d ago
  • 11 active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Concentrated ownership — top contributor handles 78% of recent commits

<sub>Computed from maintenance signals — commit recency, contributor breadth, bus factor, license, CI, tests, cross-checked against OpenSSF Scorecard</sub>

TL;DR

Tornado is a Python web framework and asynchronous networking library that uses non-blocking I/O to handle tens of thousands of concurrent connections efficiently. It's purpose-built for long-polling, WebSockets, and persistent connections, allowing developers to build real-time web applications without heavy threading overhead. Single-repo structure: core Tornado framework code in tornado/ (Python modules for web, ioloop, httpclient, etc.), runnable examples in demos/ (blog, chat, file_upload, facebook, google_auth), documentation in docs/, and CI workflows in .github/workflows/. The framework is monolithic rather than modular—you depend on the entire Tornado package, not individual components.

👥Who it's for

Python developers building real-time web applications (chat systems, live dashboards, WebSocket services), backend engineers needing high-concurrency networking without threads, and teams migrating from FriendFeed's original architecture or seeking lightweight async alternatives to Django/Flask for I/O-heavy workloads.

🌱Maturity & risk

Tornado is production-ready and actively maintained: it's a mature project originating from FriendFeed (pre-2013) with stable CI pipelines in .github/workflows/ (build.yml, test.yml), comprehensive test coverage tracked in .coveragerc, and regular updates. The codebase (~1.6MB Python) shows professional quality with CONTRIBUTING.md, SECURITY.md, and ReadTheDocs configuration.

Low risk overall: Tornado has minimal dependencies (aiopg, bcrypt, markdown, psycopg2 visible in config), well-established governance, and no obvious single-maintainer bottleneck for a mature project. Main risk: choosing Tornado requires committing to its async/await patterns rather than synchronous frameworks; breaking changes between major versions are possible but well-documented in release notes.

Active areas of work

Active development visible via CI setup (.github/workflows/test.yml and build.yml) and maintained documentation (.readthedocs.yaml), plus ongoing demo applications (blog, chat, file_upload with Docker support). Specific recent activity: demos include docker-compose.yml and Dockerfile for blog app, suggesting cloud-native tooling integration. ReadTheDocs and codecov.yml indicate continuous documentation and coverage tracking.

🚀Get running

git clone https://github.com/tornadoweb/tornado.git
cd tornado
pip install -e .
pip install aiopg bcrypt markdown psycopg2  # optional dependencies
python demos/chat/chatdemo.py  # or python demos/blog/blog.py

Daily commands:

python demos/chat/chatdemo.py  # Starts on default port
# or
python demos/blog/blog.py  # Blog demo with database
# For custom port:
python -c 'import tornado.web, tornado.ioloop; app = tornado.web.Application([(r"/", tornado.web.RequestHandler)]); app.listen(8888); tornado.ioloop.IOLoop.current().start()'

🗺️Map of the codebase

  • tornado/web.py — Core request handler and application routing framework; the primary entry point for HTTP request processing in Tornado.
  • tornado/ioloop.py — Event loop implementation that drives Tornado's non-blocking I/O model and asynchronous execution.
  • tornado/iostream.py — Non-blocking stream abstraction for TCP connections; critical for handling long-lived connections and WebSockets.
  • tornado/httpserver.py — HTTP server implementation that accepts connections and delegates to the application via IOLoop.
  • tornado/httputil.py — HTTP utilities including request/response parsing, headers, and URL handling across the framework.
  • tornado/gen.py — Coroutine support and async/await utilities that enable asynchronous code patterns in Tornado.
  • tornado/netutil.py — Network utilities for socket creation, address binding, and low-level socket configuration.

🧩Components & responsibilities

  • HTTPServer (Python socket, ssl, IOLoop) — Accepts TCP connections, orchestrates HTTP parsing, and delegates to Application routing.
    • Failure mode: Port binding failure, SSL cert errors, or accept() exceptions prevent server startup.
  • IOLoop (select/epoll/kqueue, Python asyncio, Futures) — Multiplexes I/O readiness, fires timers, and executes callbacks; the heartbeat of the framework.
    • Failure mode: Unhandled exception in callback stops the loop; blocking operation stalls all concurrency.
  • RequestHandler (HTTP parsing, template rendering, URL encoding) — Encapsulates a single HTTP request/response lifecycle with parsing, routing, and method dispatch.
    • Failure mode: Unhandled exception in get/post/etc. returns 500; missing Content-Length causes client disconnect.
  • HTTPClientConnection / iostream (IOLoop socket events, OpenSSL for TLS) — Manages non-blocking reading and writing on TCP streams with buffering and flow control.
    • Failure mode: Network errors or remote close cause EOF; buffer overflow causes memory issues or dropped messages.
  • Application (URL regex routing, handler factories) — Stores route patterns, instantiates handlers, and manages application-level state and middleware.
    • Failure mode: Malformed route patterns cause startup failure; missing handler classes raise 404.

🔀Data flow

  • Browser/ClientHTTPServer — TCP packets containing HTTP request headers and body
  • HTTPServerApplication — Parsed HTTPServerRequest object with method, path, headers, and body
  • ApplicationRequestHandler — Route match result and handler class instantiation
  • RequestHandlerIOLoop — Coroutine submission and I/O operation awaits (e.g., httpclient, database)
  • RequestHandlerHTTPServer — Response body written via self.write(), status, and headers
  • HTTPServerBrowser/Client — TCP packets containing HTTP response status, headers, and body

🛠️How to make changes

Add a new HTTP endpoint

  1. Create a new RequestHandler subclass in your application module with get(), post(), etc. methods (tornado/web.py)
  2. Add a URL pattern tuple (regex, handler_class) to the Application routes list in your app creation (tornado/web.py)
  3. Use self.write() to send responses and self.get_argument() to read query/form parameters (tornado/web.py)

Add asynchronous I/O operation

  1. Define your RequestHandler method as async def and await any I/O operation (tornado/web.py)
  2. Use tornado.httpclient.AsyncHTTPClient for outbound HTTP requests or await any Future-returning function (tornado/httpclient.py)
  3. Return responses normally with self.write(); IOLoop automatically manages the asynchronous context (tornado/ioloop.py)

Implement WebSocket support

  1. Create a handler subclassing tornado.websocket.WebSocketHandler instead of RequestHandler (tornado/websocket.py)
  2. Implement on_message(message) to handle incoming frames and on_open/on_close lifecycle methods (tornado/websocket.py)
  3. Use self.write_message() to send frames and call self.close() to terminate the connection (tornado/websocket.py)

Add middleware or request interception

  1. Override prepare() in RequestHandler to execute code before any HTTP method handler (tornado/web.py)
  2. Override finish() to execute code after the response is written but before the connection closes (tornado/web.py)
  3. Use Application(handlers, default_handler_class=MyBase) to apply cross-cutting concerns across all handlers (tornado/web.py)

🔧Why these technologies

  • Python asyncio integration — Provides native coroutine/await support and integrates with Python's standard async ecosystem.
  • Non-blocking I/O via select/epoll/kqueue — Enables handling tens of thousands of concurrent connections with minimal resource footprint.
  • WebSocket support — Delivers real-time bidirectional communication without polling overhead.
  • Template engine (Tornado template language) — Provides server-side rendering with autoescaping to prevent injection attacks.

⚖️Trade-offs already made

  • Single-threaded event loop per process

    • Why: Avoids locking overhead and race conditions in the core event system.
    • Consequence: CPU-bound work blocks the loop; multi-process or threadpool patterns required for heavy computation.
  • No built-in ORM or database abstraction

    • Why: Keeps framework lightweight and allows users to choose their preferred async database driver.
    • Consequence: Developers must manage database connectivity and async driver selection themselves.
  • Explicit async/await patterns rather than automatic magic

    • Why: Makes async boundaries visible and code flow predictable.
    • Consequence: Requires developers to understand async programming; more verbose than sync frameworks.

🚫Non-goals (don't propose these)

  • Does not provide ORM or database layer; relies on external libraries (aiopg, asyncpg, etc.)
  • Does not include built-in authentication; auth is left to application or middleware
  • Does not handle clustering or distributed request routing; single-process or external load balancing required
  • Does not provide GraphQL, gRPC, or REST API frameworks; focuses on low-level HTTP/WebSocket

📊Code metrics

  • Avg cyclomatic complexity: ~6.5 — Heavy use of asyncio, callback chains, and state machine patterns (HTTP parsing, stream buffering, connection lifecycle) elevate complexity; mitigated by clear separation of concerns.
  • Largest file: tornado/web.py (2,200 lines)
  • Estimated quality issues: ~15 — Python 3.8+ asyncio compatibility maintained; comprehensive test coverage; occasional linting deviations and legacy generator-based coroutine code still present for backward compatibility.

⚠️Anti-patterns to avoid

  • Blocking operations in async handlers (High)tornado/web.py RequestHandler methods: Calling time.sleep(), synchronous I/O, or CPU-bound code in async handlers blocks the entire IOLoop and stalls other connections.
  • Missing error handling in callbacks (High)tornado/ioloop.py callback execution: Unhandled exceptions in IOLoop callbacks can silently crash the loop or leave it in an inconsistent state.
  • Shared mutable state without synchronization (Medium)tornado/web.py Application-level state: Multiple request handlers accessing shared dicts/lists without locks can cause race conditions in multi-process deployments.
  • Improper Future handling (Medium)tornado/gen.py coroutine decorators: Forgetting to await Futures or yielding in generator-style code causes silent failures or stale data.

🔥Performance hotspots

  • tornado/ioloop.py - IOLoop.start() (Concurrency & CPU) — Single-threaded event loop becomes CPU bottleneck for high-load scenarios; CPU-bound handlers require offloading to executor.
  • tornado/iostream.py - _read_buffer accumulation (Memory & Buffer Management) — Large inbound payloads without streaming or chunking can exhaust memory if read_bytes is set too high.
  • tornado/web.py - Application route matching (Routing Performance) — Linear scan of regex patterns for every request; large route tables may incur regex compilation overhead.
  • tornado/httpserver.py - connection accept loop (Socket Scalability) — Thundering herd problem if many processes listen on same port; OS kernel serializes accept() calls.

🪤Traps & gotchas

  1. asyncio event loop assumption: Tornado runs its own IOLoop by default; if you mix it with other async libraries (like aiohttp), you must manage the loop carefully or use tornado.platform.asyncio. 2) Callback hell without async/await: Older Tornado code uses tornado.gen.coroutine; upgrade to async/await to avoid confusion. 3) Blocking I/O hangs the server: Any blocking call (e.g., time.sleep(), synchronous database query) blocks the entire event loop; use async versions (asyncio.sleep, aiopg) or run_on_executor(). 4) XSRF tokens required by default: RequestHandler sets up XSRF protection; disable with check_xsrf_cookie = False if building APIs, or pass _xsrf_token in forms. 5) Template auto-escaping: Tornado auto-escapes template variables; use {% raw %} or raw_xhtml=True if needed.

🏗️Architecture

💡Concepts to learn

  • Non-blocking I/O and event-driven architecture — Tornado's entire value proposition rests on handling thousands of concurrent connections via a single event loop instead of threads; understanding select/epoll/kqueue is essential to grok why it exists
  • WebSocket protocol and persistent connections — WebSockets are the primary use case Tornado optimizes for; knowing the handshake, frame format, and close semantics prevents bugs in real-time apps
  • Async/await coroutines and futures — Tornado code is written as async functions; understanding how Python's asyncio.Future and async/await desugar to callbacks is critical for debugging and reasoning about execution order
  • Long polling and push mechanisms — Tornado's original motivation; knowing when to use long-polling vs WebSockets vs Server-Sent Events helps choose the right Tornado pattern
  • Context-local storage (thread-local equivalent for async) — Tornado provides tornado.web.RequestHandler.current_user and context APIs for storing per-request data in an async context; misunderstanding can leak data between concurrent requests
  • XSRF (Cross-Site Request Forgery) protection — Tornado enables XSRF checks by default in RequestHandler; disabling or misconfiguring it is a common security mistake in production apps
  • Server-Sent Events (SSE) — An alternative to WebSockets for server-to-client push supported by Tornado's streaming response; useful when full-duplex communication isn't needed
  • aio-libs/aiohttp — Modern async HTTP framework for Python with similar concurrency model but more opinionated web server structure; direct competitor for new projects
  • django/django — Synchronous web framework with ORM, admin panel, and batteries-included features; Tornado trades these conveniences for raw async performance
  • pallets/flask — Lightweight synchronous framework; similar minimalist philosophy to Tornado but blocking I/O, used when async is not a requirement
  • tornadoweb/tornado-http2 — Official Tornado extension adding HTTP/2 support; ecosystem companion for modern protocol requirements
  • crossbario/autobahn-python — WebSocket and WAMP library often used alongside Tornado for advanced real-time messaging patterns

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive test coverage for demos/ with integration tests

The demos directory contains 8+ demo applications (blog, chat, websocket, file_upload, google_auth, etc.) but there's no evidence of automated tests validating these examples work correctly. This is high-value because: (1) demos are often the first thing new users try, (2) broken demos damage project reputation, (3) demos serve as integration tests for core framework features. Adding CI tests that spin up each demo and validate basic functionality would catch regressions early.

  • [ ] Create tests/test_demos.py to import and validate each demo app initializes without errors
  • [ ] Add a GitHub Actions workflow step in .github/workflows/test.yml to run demo tests
  • [ ] For demos/blog: test that the SQLite schema initializes and basic routes respond
  • [ ] For demos/chat and demos/websocket: validate WebSocket handler registration
  • [ ] For demos/file_upload: test file upload endpoint configuration
  • [ ] Document in CONTRIBUTING.md that demo changes require corresponding test updates

Add type hints and mypy configuration for tornado/web.py core module

The main RequestHandler and Application classes in tornado/web.py are foundational but likely lack complete type hints. This is valuable for: (1) improving IDE autocomplete for users, (2) catching type-related bugs early via CI (currently no mypy in visible workflows), (3) making the codebase more maintainable. Starting with the web module sets a precedent for the rest of the framework.

  • [ ] Add mypy configuration to setup.cfg or pyproject.toml with tornado/ as target
  • [ ] Add type hints to tornado/web.py RequestHandler class methods (get, post, write, set_header, etc.)
  • [ ] Add type hints to tornado/web.py Application.init and routing configuration
  • [ ] Add mypy check to .github/workflows/test.yml to fail on type errors
  • [ ] Update CONTRIBUTING.md to require type hints for new code in core modules

Create missing async/await migration tests for tornado/ioloop.py and tornado/iostream.py

Tornado supports both callback-based and async/await patterns, but with only the partial file structure visible, testing for proper async/await compatibility across core I/O modules is critical. Modern Python users expect async/await support to be robust. This is high-value because: (1) async/await is the standard in Python 3.7+, (2) mixed callback/async patterns can cause subtle bugs, (3) comprehensive tests ensure framework stability across both paradigms.

  • [ ] Review tornado/ioloop.py and tornado/iostream.py for async/await method coverage
  • [ ] Add tests/test_ioloop_async.py with async/await versions of existing ioloop callback tests
  • [ ] Add tests/test_iostream_async.py covering read_until, read_bytes with async/await
  • [ ] Add tests validating mixing callbacks and async/await in the same event loop doesn't cause hangs
  • [ ] Add a CI step in .github/workflows/test.yml to specifically run async/await tests on Python 3.9+

🌿Good first issues

  • Add type hints to tornado/ioloop.py: The event loop module has minimal type annotations despite Python 3.5+ support. Adding PEP 484 hints would improve IDE autocomplete and catch bugs early.
  • Expand demos/google_auth/ to include a full working example: The google_auth demo directory exists but appears minimal (seen in file list). A complete OAuth flow example with error handling would match the completeness of demos/blog/.
  • Document WebSocket reconnection patterns in docs/: Tornado has websocket.py but no canonical guide on client-side reconnection, backoff, or fallback to long-polling. Adding a tutorial would reduce support questions.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 3239945 — Merge pull request #3648 from bdarnell/update-contributing (bdarnell)
  • 657e1d7 — docs: Update CONTRIBUTING.md (bdarnell)
  • 6d2bf68 — Merge pull request #3647 from bdarnell/locks-queues-docs (bdarnell)
  • fbaeb23 — locks,queues: Add versionchanged docs for #3621 (bdarnell)
  • 18e15a5 — Merge pull request #3621 from SAY-5/fix-zero-timeout (bdarnell)
  • affd793 — Merge pull request #3646 from bdarnell/httpserverrequest-deprecation (bdarnell)
  • 41f931c — httputil: Deprecate some args to HTTPServerRequest constructor (bdarnell)
  • e952004 — Merge pull request #3638 from Psychosoc1al/pycurl_types_update (bdarnell)
  • 742616f — Update pycurl annotations compatibility (Psychosoc1al)
  • f491e4c — Merge pull request #3634 from bdarnell/curlreset (bdarnell)

🔒Security observations

  • Medium · Bcrypt Dependency Version Not Specified — Dependencies/Package file (bcrypt). The bcrypt dependency is listed without version constraints. This could lead to unexpected behavior if a breaking change is released. Bcrypt is critical for password hashing security. Fix: Pin bcrypt to a specific version or version range (e.g., bcrypt>=4.0.0,<5.0.0) to ensure consistent security implementations across deployments.
  • Medium · Missing CSRF Protection Documentation — demos/blog/blog.py and related templates. While Tornado has built-in CSRF protection, the repository doesn't show explicit CSRF token validation in the visible demo applications. The blog demo in particular handles user input without visible CSRF protection mechanisms. Fix: Ensure all forms include CSRF tokens via _xsrf parameter. Review and update demo applications to demonstrate secure CSRF practices with tornado.web.RequestHandler's xsrf_token support.
  • Medium · SQL Injection Risk in Blog Demo — demos/blog/blog.py and demos/blog/schema.sql. The blog demo includes schema.sql and blog.py which may contain raw SQL queries. Without visible parameterized query usage, there is potential SQL injection risk, especially if user input is used in database queries. Fix: Use parameterized queries with proper ORM/database drivers (e.g., aiopg with parameter binding). Never concatenate user input into SQL strings. Validate and sanitize all inputs.
  • Medium · Unverified External Authentication in Demo — demos/facebook/facebook.py and demos/google_auth/main.py. Facebook and Google authentication demos are present. OAuth callback validation and state parameter handling must be implemented correctly to prevent authorization code injection attacks. Fix: Verify OAuth state parameter matches request state. Validate redirect_uri, use HTTPS only, and never log sensitive tokens. Implement proper error handling for failed authentications.
  • Low · WebSocket Security Considerations — demos/websocket/chatdemo.py and demos/chat/chatdemo.py. WebSocket demos exist without visible authentication/authorization checks. WebSocket connections should require proper authentication before accepting messages. Fix: Implement authentication middleware for WebSocket connections. Validate user identity before accepting messages. Use WSS (WebSocket Secure) in production only.
  • Low · Missing Security Headers Configuration — Configuration/Application setup (demos and main framework). No visible configuration for security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security) in provided code snippets. Fix: Configure security headers in RequestHandler or middleware. Use Tornado's set_header() to add: X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Content-Security-Policy, and HSTS headers.
  • Low · File Upload Security in Demo — demos/file_upload/file_receiver.py and file_uploader.py. File upload demo exists (demos/file_upload/) but security validations like file type checking, size limits, and path traversal prevention are not visible. Fix: Implement strict file type validation (whitelist), enforce file size limits, use secure file naming, store uploads outside web root, and prevent directory traversal with os.path.abspath() validation.
  • Low · Markdown Dependency Usage — Dependencies/Package file (markdown) and usage throughout codebase. Markdown is listed as a dependency. If used for user-generated content without HTML sanitization, it could allow XSS attacks through markdown to HTML conversion. Fix: If processing user-provided markdown, sanitize the resulting HTML using libraries like bleach. Never trust markdown output directly. Use allowlist-based HTML sanitization.
  • Low · Database Credentials in Demo Docker Compose — demos/blog/docker-compose.yml and demos/blog/requirements.txt. Docker Compose files exist (demos/blog/docker-compose.yml) which may contain database credentials or default passwords. Fix: Use environment variables for all credentials in Docker Compose. Never commit default passwords. Use .env files with .gitignore protection. Reference SECURITY.md for credential management guidelines.

LLM-derived; treat as a starting point, not a security audit.

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/tornadoweb/tornado shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live tornadoweb/tornado repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/tornadoweb/tornado.

What it runs against: a local clone of tornadoweb/tornado — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in tornadoweb/tornado | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>tornadoweb/tornado</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of tornadoweb/tornado. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/tornadoweb/tornado.git
#   cd tornado
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of tornadoweb/tornado and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "tornadoweb/tornado(\\.git)?\\b" \\
  && ok "origin remote is tornadoweb/tornado" \\
  || miss "origin remote is not tornadoweb/tornado (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "tornado/web.py" \\
  && ok "tornado/web.py" \\
  || miss "missing critical file: tornado/web.py"
test -f "tornado/ioloop.py" \\
  && ok "tornado/ioloop.py" \\
  || miss "missing critical file: tornado/ioloop.py"
test -f "tornado/iostream.py" \\
  && ok "tornado/iostream.py" \\
  || miss "missing critical file: tornado/iostream.py"
test -f "tornado/httpserver.py" \\
  && ok "tornado/httpserver.py" \\
  || miss "missing critical file: tornado/httpserver.py"
test -f "tornado/httputil.py" \\
  && ok "tornado/httputil.py" \\
  || miss "missing critical file: tornado/httputil.py"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/tornadoweb/tornado"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Embed this chat in your README →

Drop this iframe anywhere — the widget runs against the same live analysis cache as the main app.

<iframe
  src="https://repopilot.app/embed/tornadoweb/tornado"
  width="100%" height="500"
  style="border:1px solid #d0d7de; border-radius:8px;"
  allow="microphone"
  loading="lazy"
></iframe>