15 KiB
AGENTS.md - Guidelines for AI Contributions to Zulip
This file provides guidance to AI coding agents for contributing to the Zulip codebase. These guidelines are designed to produce contributions that meet the same high standards we expect from human contributors.
When doing any work with AI assistants, it is crucial to follow our
AI use policy and guidelines (the "AI use policy and guidelines"
section in CONTRIBUTING.md). In particular:
Do not submit an AI-generated PR you haven't personally understood and tested, as this wastes maintainers' time. PRs that appear to violate this guideline will be closed without review.
Do not post AI-generated messages in the Zulip development community -- we want to read your own genuine expression of your thoughts.
Claude Code automatically uses .claude/rules/ and .claude/skills/.
If you are a different agent, read the files in these folders to see
the rest of the agent instructions and when they're relevant. Rules
are relevant for files listed in paths: at the top of the rule
file. Skills explain when they're relevant in their description.
Philosophy
Zulip is a team chat application used by thousands of organizations, built to last for many years. It is developed by a vibrant open-source community, with maintainers who have consistently emphasized high standards for codebase readability, code review, commit discipline, debuggability, automated testing, tooling, documentation, and all the other subtle details that together determine whether software is easy to understand, operate, and modify.
Zulip's engineering strategy is to "move quickly without breaking things". This is possible because the project has invested years in testing, tooling, code structure, documentation, and development practices that catch bugs systematically rather than relying on individual vigilance. Maintainers spend most of their review time on product decisions and code structure/readability, not on chasing correctness issues — because the process is designed to prevent them.
This means Zulip's coding philosophy is to focus relentlessly on making the codebase easy to understand and difficult to make dangerous mistakes. This applies equally to AI-generated contributions. Every change should make the codebase more maintainable and easier to read.
No detail is too small
There is no category of "minor issue" that is acceptable to ship —
if something is broken in any state, size, theme, or language where
a user would encounter it, it must be fixed before merging. If a fix
would require a design decision, raise it as a question rather than
shipping the broken state. See .claude/rules/ui-testing.md for
what to test for UI changes (the file loads automatically when you
work on frontend files).
The right attitude is: "What could go wrong, and how do I verify that it doesn't?" not "It looks fine to me." What isn't tested probably doesn't work — this applies to visual changes just as much as to backend logic.
Understand before coding
Before writing any code, you must understand:
- What the existing code does and why, including the relevant help center or developer-facing documentation.
- What problem you're solving, in its full scope.
- Why your approach is the right solution, and available alternatives.
- How you will verify that your work is correct, and avoid regressions that are plausible for the type of work you're doing.
The answer to "Why is X an improvement?" should never be "I'm not sure."
Workflow
Follow this workflow for every task: understand → propose → implement → verify.
1. Understand Before Coding
Before making any changes:
# Read relevant documentation
cat docs/*/<relevant-area>.md
cat starlight_help/src/content/docs/<topic>.md
cat api_docs/<topic>.md and read the relevant part of zerver/openapi/zulip.yaml
# Look at existing code patterns
git grep "similar_function_name"
git log --oneline -20 -- path/to/file.py
# Check for related issues on GitHub
2. Propose an Approach
Before writing code, explain the plan:
- Explain your understanding of the problem and all relevant design decisions
- What changes are needed and why
- How the changes fit with existing patterns
- What could break and how to prevent regressions
3. Implement in Minimal, Coherent Commits
Structure changes as clean commits:
- Backend and API changes, with tests and API doc changes documented
fully using our double-entry changelog system. Instructions for
documentation can be found in
.claude/rules/api-changelog.md(loaded automatically when you editzerver/openapi/zulip.yaml). - Frontend UI changes (with tests and user-facing documentation
updates). Remember to plan to use your visual test skill
(
.claude/skills/visual-test/SKILL.md) to check your work whenever you change web app code (HTML, CSS, JS).
Each commit should be self-contained, highly readable and reviewable
using git show --color-moved, and pass lint/tests independently. If
extracting new files or moving code, always do that in a separate
commit from other changes.
4. Verify Before Finalizing
Run the linter and the relevant tests before making each commit; see "Testing Requirements" below for instructions.
Run through the /self-review skill's checklist before suggesting opening
a PR (.claude/skills/self-review/SKILL.md).
Before You Start
Read the Relevant Documentation
Zulip has over 185,000 words of developer documentation. Before working on any area:
- Read documentation from docs/, starlight_help/src/content/docs/, and api_docs/.
docs/subsystems/directory-structure.mdexplains where code lives. - Read existing code in the area you're modifying.
- Use
git grepto find similar patterns in the codebase and read those.
Understand the Code Style
- Be consistent with existing code. Look at surrounding code and follow the same patterns, as this is a thoughtfully crafted codebase.
- Use clear, greppable names for functions, arguments, variables, and
tests. Future developers will
git grepfor relevant terms when researching a problem, so names should communicate purpose clearly. - Keep everything well factored for maintainability. Avoid duplicating code, especially where access control or subtle correctness is involved.
- Run
./tools/lintto catch style issues before committing, including mypy issues. - Prefer writing code that is readable without explanation over heavily commented code using clever tricks.
- Don't reference line numbers (e.g.,
filter.ts:493) in comments, commit messages, or PR descriptions; they get stale when files are edited. Reference symbol names instead.
Comments
- Comments should explain "why" when the reason isn't obvious, not narrate "what" the code does.
- Comments should have a line to themself except for CSS px math.
- Comments should never describe the change being made or a bug being fixed. They should read as if they were there all along. Descriptions of the diff belong in the commit message instead.
- Match comment density of nearby code and similar functions. If similar code doesn't comment on something, don't comment on it in new code.
- Avoid metaphors in favor of clear descriptive language. Match
existing idioms verbatim for comments describing specific
situations (e.g.,
/* Override bootstrap defaults */) rather than writing a new or longer explanation.
Further rules
Frontend rules can be found in .claude/rules/frontend.md (loaded
automatically when you work on frontend JS/TS and template files).
CSS rules can be found in .claude/rules/css.md (loaded
automatically when you work on CSS files).
Python rules can be found in .claude/rules/python.md (loaded
automatically when you work on .py files).
See: docs/contributing/code-style.md
Commit Discipline
Zulip follows the Git project's practice of "Each commit is a minimal coherent idea." This is non-negotiable.
Each Commit Must:
- Be coherent: Implement one logical change completely and atomically.
- Pass tests: Include test updates in the same commit as code changes.
- Not make Zulip worse: Work is ordered so no commit has regressions.
- Be safe to deploy individually: Or explain in detail why not.
- Be minimal and reviewable: Don't combine moving code with changing it in the same commit; make liberal use of small prep commits for no-op refactoring that are easy to verify.
Never:
- Mix multiple separable changes in a single commit.
- Create a commit that "fixes" a mistake from an earlier commit in the same PR; always edit Git to fix the original commit.
- Add content in one commit only to remove or move it in the next; plan upfront what belongs where and do it right the first time.
- Include debugging code, commented-out code, or temporary TODOs.
- Leave commits that break if a later commit in the PR is dropped. When a commit is flagged as potentially droppable, verify all earlier commits work correctly without it.
Open Independently Useful Fixes as Separate PRs
If, while building a feature, you find and fix a pre-existing bug or make a refactor that would be worth merging even if the feature never lands, split this into its own PR. A small isolated PR gets more scrutiny than the same change as part of a large PR. Prep commits that mainly make sense in the context of the feature PR should stay in the feature's PR.
Commit Message Format
Use the /commit-message skill whenever you write or reword a
commit message (.claude/skills/commit-message/SKILL.md).
Testing Requirements
Zulip server takes pride in its ~98% test coverage. All server changes must include nice tests that follow our testing philosophy.
When writing tests, follow our testing philosophy in
.claude/rules/testing.md (loaded automatically when you work on
test files).
Manage your time by running specific backend test collections, not the entire suite; the node suite is fast enough to run in full.
# Includes mypy and typescript checkers
./tools/lint path/to/changed/files.py
./tools/test-backend zerver.tests.test_relevant_module
./tools/test-js-with-node
Manual Testing for UI Changes
If a PR makes frontend changes, manually verify the affected UI
using the checklist in .claude/rules/ui-testing.md (loaded
automatically when you work on frontend files).
What Not To Do
- Always check if you're working on top of the latest upstream/main, and fetch + rebase when starting a project so you're not using a stale branch. If you're continuing a project, start by rebasing, resolving merge conflicts carefully.
- Don't make design or UX decisions silently. When a technical constraint forces a tradeoff, present the constraint and options to the user rather than picking one. Never remove features, hide UI elements, or change interaction patterns without asking.
- Don't submit code you haven't tested
- Don't skip becoming familiar with the code you're modifying
- Don't make claims about code behavior without verification, and cite your sources.
- Don't generate PR descriptions that just describe what files changed
- Always do a pre-mortem: Think about how to avoid a bug recurring, how it might break something that already works, or imagine under what circumstances your changes might need to be reverted.
Pull Request Guidelines
When opening a pull request, prefix the PR title with [ai] (e.g.,
[ai] compose: Fix cursor position after emoji insertion.). Use
upstream/main as the base branch. Use the /pr-description skill
to write the PR description
(.claude/skills/pr-description/SKILL.md).
When to Pause and Discuss
Recommend pausing for discussion when:
- The approach involves security-sensitive code
- Database migrations are needed (See
docs/subsystems/schema-migrations.md). - The change alters behavior in several subsystems at once (renames and type annotation changes that touch many files do not count)
- Performance implications are unclear
- The feature design isn't fully specified
- The API or data model design isn't fully specified
- An API change may not be compatible (See
docs/processes/api-design.md). - Existing tests are failing for unclear reasons
Task-Specific Approaches
For Bug Fixes
- Look at the relevant code and brainstorm theories for how the bug might be possible
- Provide a clear explanation for the bug, and ideally
provide steps for reproducing the bug on
mainin the dev environment. Verify the cause of the bug before suggesting a fix, unless a bug is very difficult to verify, in which case say so and explain a hypothesis instead. - Analyze and propose a fix with a clear explanation
- Write tests that would have caught this bug if possible
- Audit for whether the bug may exist elsewhere or might be re-introduced and propose appropriate changes to address if so.
For New Features
- Read the relevant documentation in docs/
- Show similar existing features in the codebase
- Propose an implementation approach before coding
- Implement, following "Commit Discipline" above
For Refactoring
- Show the current implementation
- Explain what makes it problematic
- Propose the refactoring approach
- Implement in commits that each leave the codebase working
- No behavior changes unless explicitly discussed
- Verify completeness: use
git grepto find all occurrences and confirm nothing was missed
Key Documentation Links
- Contributing guide:
docs/contributing/contributing.md - Code style:
docs/contributing/code-style.md - Commit discipline:
docs/contributing/commit-discipline.md - Testing overview:
docs/testing/testing.md - Backend tests:
docs/testing/testing-with-django.md - Code review:
docs/contributing/code-reviewing.md - mypy guide:
docs/testing/mypy.md
Help Center Documentation
When making any user-facing change, read
docs/documentation/helpcenter.md in full and review the relevant
help center articles under starlight_help/src/content/docs/ for any
updates that should be made. The writing guide there is the source
of truth for help center conventions, components, and structure.
Zulip Chat Links
When you encounter a Zulip narrow URL (e.g., from chat.zulip.org in a
GitHub issue, PR, or user message), use the /fetch-zulip-messages
skill (.claude/skills/fetch-zulip-messages/SKILL.md) to read the
conversation. Do not use WebFetch — it cannot access Zulip message
content.
Common Commands
./tools/provision # Set up development environment
./tools/run-dev # Start development server
./tools/lint # Run all linters
./tools/test-backend # Run Python tests
./tools/test-js-with-node # Run JavaScript tests
./tools/run-mypy # Run type checker
git grep "pattern" # Search codebase (use extensively!)
Most of these commands take minutes, so run them in the background.
If a tool complains that provision is outdated, run ./tools/provision
to fix it. Do not use --skip-provision-check to work around the
error; the check exists because tests and linters depend on provisioned
dependencies being current.
If the development environment runs in a container (Vagrant/Docker),
run ./tools/ commands inside it. Files and new screenshots will be
accessible on both sides.
vagrant ssh -c 'cd ~/zulip && ./tools/lint path/to/changed/files.py'
If vagrant ssh fails with a Docker daemon error, Docker Desktop isn't
running; ask the user to start it.