Stage 1 — Docs and Tests
What this stage teaches
Stage 1 is the on-ramp. The skills are deliberately non-technical — they are the GitHub contribution workflow that every later stage assumes and never re-explains:
- Claim an issue by commenting, then fork and branch.
- Sign every commit off (DCO,
git commit -s) so theSigned-off-by:line is present. - Add the one-line CHANGELOG.md entry that OpenSearch requires on every PR.
- Run
./gradlew precommitand read its output without panicking. - Open a Pull Request, read the CI (GitHub Actions + Jenkins gradle-check), and respond to review by pushing more commits to the same branch.
The contributions themselves are surgical: a docs typo, a stale config key in a .md, a
javadoc that the build flags, a weak or missing assertion in an existing unit test.
Nothing in this stage will surprise a reviewer. That is the point — you are exercising the
workflow so the next stages can be about code.
Note: If you are arriving from the Apache Tez roadmap: there is no JIRA, no
.patchfile, no "Submit Patch" button. The unit of work is a branch on your fork and a PR againstopensearch-project/OpenSearch. Re-rolls are extra commits (or a force-push, with etiquette — see Pitfalls), not new attachments.
The GitHub workflow, end to end
Step 0 — Fork and clone
gh repo fork opensearch-project/OpenSearch --clone # or fork in the UI, then:
git clone git@github.com:<you>/OpenSearch.git
cd OpenSearch
git remote add upstream https://github.com/opensearch-project/OpenSearch.git
git fetch upstream
Keep main clean and tracking upstream. Branch for every change:
git checkout -b docs/fix-rolling-upgrade-typo upstream/main
Step 1 — Find an issue
Paste this into the issue search box on the OpenSearch repo (https://github.com/opensearch-project/OpenSearch/issues):
is:issue is:open label:"good first issue" no:assignee sort:updated-desc
Narrow to docs/test work by adding a keyword:
is:issue is:open label:"good first issue" no:assignee docs in:title,body
is:issue is:open label:"good first issue" no:assignee "javadoc" in:title,body
is:issue is:open label:"good first issue" no:assignee "test" "assert" in:body
Open three candidates. Read each thread end to end. Choose one that has no assignee,
no open PR linked, and is not labelled untriaged (untriaged means a maintainer has not
confirmed it is real). Comment to claim it:
I'd like to work on this. Planning to <one sentence>. Could a maintainer assign it to me?
OpenSearch does not require an explicit assignment to open a PR, but commenting prevents two people doing the same work and signals the maintainers to triage.
If no labelled issue fits, file your own from a real defect you find. Grep the docs in
the repo (most user docs live in the separate documentation-website repo, but the core
repo carries README, CONTRIBUTING.md, DEVELOPER_GUIDE.md, TESTING.md, package-info
javadoc, and inline javadoc):
grep -rn "cluster.initial_master_nodes" . --include=*.md --include=*.java | head
grep -rn "TODO\|FIXME\|XXX" DEVELOPER_GUIDE.md TESTING.md CONTRIBUTING.md
grep -rn "elasticsearch" docs/ 2>/dev/null | head # stale fork references
A genuine doc or stale-reference bug found this way is fair game for your first PR. Open
an issue describing it first, then the PR that fixes it (the PR links the issue with
Closes #NNNN).
Walked example 1 — a javadoc / package-info documentation fix
This example is illustrative of the pattern, not a transcription of one specific PR. The grep finds the real site on your branch; class and line numbers vary by branch.
Symptom: a user reports on the forum that org.opensearch.common.settings.Setting
has a public Property enum value whose javadoc does not say what it does, so plugin
authors cannot tell when to use it.
Locate the code
grep -rn "enum Property" server/src/main/java/org/opensearch/common/settings/Setting.java
git log --oneline -n 5 -- server/src/main/java/org/opensearch/common/settings/Setting.java
Open the file. The relevant block looks roughly like:
public enum Property {
Filtered,
Dynamic,
Final,
Deprecated,
NodeScope,
IndexScope,
// ...
}
Several values have no javadoc. That is the bug.
Diff
--- a/server/src/main/java/org/opensearch/common/settings/Setting.java
+++ b/server/src/main/java/org/opensearch/common/settings/Setting.java
@@
public enum Property {
- Filtered,
+ /** The setting's value is sensitive and must be redacted from APIs and logs. */
+ Filtered,
+ /** The setting can be changed at runtime via the cluster/index update settings API. */
Dynamic,
+ /** The setting can be set once at index creation and never changed afterwards. */
Final,
Two rules for documentation diffs:
- Describe behaviour, not the identifier. "
Dynamicmeans dynamic" is noise. Say what changes for the user when the property is present. - Verify against the code, not your memory. Trace where the enum value is read
(
grep -rn "Property.Dynamic" server/) and confirm your sentence matches the actual check before you write it.
Build the documentation gate
OpenSearch validates javadoc as part of precommit. For a docs-only change you do not
need the full test suite — run the targeted gates:
./gradlew :server:compileJava -q # does it still compile?
./gradlew :server:checkstyleMain -q # style (line length, ordering)
./gradlew spotlessJavaCheck -q # formatting
./gradlew :server:precommit # the full per-project gate (javadoc, headers, forbidden-APIs)
If spotlessJavaCheck fails, fix it mechanically: ./gradlew spotlessApply.
CHANGELOG and commit
Every PR must add a line to CHANGELOG.md under the ## [Unreleased ...] heading, in the
right category (Added / Changed / Fixed / Deprecated / Removed / Security):
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ ## [Unreleased 3.x]
### Changed
+- Document the `Setting.Property` enum values for plugin authors ([#NNNNN](https://github.com/opensearch-project/OpenSearch/pull/NNNNN))
The PR number is not known yet; open the PR, then come back and fix the link in a follow-up commit (maintainers accept the placeholder being updated after the number exists).
Commit with sign-off:
git add server/.../Setting.java CHANGELOG.md
git commit -s -m "Document Setting.Property enum values"
The -s adds the Signed-off-by: Your Name <you@example.com> line that the DCO check
requires. Configure user.name/user.email first so it matches your GitHub identity;
the DCO bot compares them.
Open the PR
git push origin docs/document-setting-property
gh pr create --repo opensearch-project/OpenSearch --fill
Fill in the PR template: link the issue (Closes #NNNN), tick the CHANGELOG and DCO
checkboxes honestly, and describe the change in two sentences. Then watch CI:
- DCO — green only if every commit is signed off.
- CHANGELOG verifier — green only if you touched
CHANGELOG.md. - gradle-check / GitHub Actions — compile, precommit, and (for code) tests.
Read failures top to bottom. A docs PR that fails gradle-check almost always failed
checkstyle or spotless, both of which the local commands above would have caught.
Walked example 2 — strengthening a weak unit-test assertion
Illustrative of the pattern. Run the grep to find a real candidate.
Symptom: an OpenSearchTestCase subclass exercises a parser but asserts only that the
result is non-null — a regression that produced a wrong value would still pass. This is
exactly the kind of issue tagged good first issue + test.
Locate a candidate
# Tests that assert non-null but never assert the value — a classic weak assertion smell:
grep -rn "assertNotNull" server/src/test/java/org/opensearch/ | head -40
Pick one where the parsed object has fields you can check. Read the test and the class under test together.
Diff
--- a/server/src/test/java/org/opensearch/common/unit/TimeValueTests.java
+++ b/server/src/test/java/org/opensearch/common/unit/TimeValueTests.java
@@
public void testParseTimeValue() {
TimeValue value = TimeValue.parseTimeValue("10s", "test");
- assertNotNull(value);
+ assertNotNull(value);
+ assertEquals(10, value.seconds());
+ assertEquals(TimeUnit.SECONDS, value.timeUnit());
+ assertEquals("10s", value.getStringRep());
}
Warning: Do not change the production class in a Stage 1 test PR. If strengthening the assertion reveals a real bug (the value is genuinely wrong), that is a Stage 3 bug fix — open a separate issue and PR. Mixing a test improvement with a behaviour change is the fastest way to get a Stage 1 PR bounced.
Run just this test
./gradlew :server:test --tests "org.opensearch.common.unit.TimeValueTests" -q
# reproduce a randomized failure later with the printed seed:
./gradlew :server:test --tests "org.opensearch.common.unit.TimeValueTests" -Dtests.seed=<SEED>
Then CHANGELOG (category Changed or no entry if the maintainers say test-only changes
are exempt — check the PR template; OpenSearch generally still wants a line), commit with
-s, push, open the PR.
Pitfalls
- Scope creep. "While I was in the file I also fixed…" is the number-one reason a Stage 1 PR sits for months. One logical change, one CHANGELOG line. File a follow-up issue for everything else.
- Missing CHANGELOG entry. The CHANGELOG verifier will fail your PR. Add the line in
the correct category under
## [Unreleased ...], not under a released version heading. - Missing DCO. If you forgot
-s, the DCO check fails. Fix the whole branch withgit rebase --signoff upstream/main(orgit commit --amend -sfor a single commit), then force-push. - Force-push etiquette. Before a maintainer has reviewed, force-push freely to keep
history clean. After review has started, prefer adding follow-up commits so reviewers
can see what changed; squash at the end if asked. OpenSearch typically squash-merges,
so you do not need a pristine history — you need a reviewable diff. Never force-push in a
way that drops a maintainer's
suggested changecommit. - Editing user docs in the wrong repo. Most end-user documentation lives in
opensearch-project/documentation-website, not the core repo. Confirm which repo owns the text before you open a PR; a doc fix in the wrong repo wastes a review cycle. mastervscluster manager. If your docs fix touches node-role text, use cluster manager (formerly master) — do not reintroducemaster-only wording.- Running the whole suite for a one-line change.
./gradlew checkruns everything and takes a long time. For docs use the targeted gates above; for a single test use--tests.
Exit criteria — when you're ready for Stage 2
You can move on when:
- You have one merged docs/javadoc PR and one merged test-only PR
(a strengthened assertion or a missing
@Testfor an existing class). - You have responded to at least one round of reviewer nits by pushing follow-up commits,
without a maintainer having to explain
git commit -sor the CHANGELOG to you. - A green
gradle-checkno longer makes you anxious, and you can read a red one and tell whether the failure is yours or a pre-existing flake (Stage 9 territory). - You can recite the loop from memory: fork → branch → change → CHANGELOG →
git commit -s→./gradlew precommit→ push → PR → read CI → respond with follow-up commits.
When to file a follow-up
If, while fixing a Stage 1 issue, you find a bigger problem — say the javadoc was missing
because a setting was renamed without @Deprecated on the old key — do not bundle the
bigger fix. File a follow-up issue:
While documenting Setting.Property (#NNNN) I noticed `index.foo` was renamed from
`index.bar` without an @Deprecated alias. Filing this to track the deprecation cleanup —
happy to take it as a Stage 2/3 change.
Narrow Stage 1 PR + follow-up issue is exactly what reviewers mean by "keep PRs focused." It is the discipline the entire roadmap depends on. Move on to Stage 2 — Build, Dependency, and Logging.