Skip to content

fix(sdk): README doc-lies, actionable errors, and a compile-checked Hello World#1522

Closed
ytallo wants to merge 2 commits into
mainfrom
worktree-sdk-dx-fixes
Closed

fix(sdk): README doc-lies, actionable errors, and a compile-checked Hello World#1522
ytallo wants to merge 2 commits into
mainfrom
worktree-sdk-dx-fixes

Conversation

@ytallo
Copy link
Copy Markdown
Contributor

@ytallo ytallo commented Apr 21, 2026

Summary

  • Fixes four public-facing README snippets that either don't compile or reference methods that don't exist (iii.connect() in Python, register_trigger(3-arg) + TriggerRequest::new + stale 0.3 version in Rust, ? on register_worker).
  • Adds TriggerRequest::new/with_action/with_timeout_ms to the Rust SDK so the README (and your callers) can stop writing the four-field struct literal for the 80% case.
  • Rewrites the NotConnected error per SDK from "iii is not connected" to a message that names the URL, how to start the engine, and links to https://iii.dev/docs/install — the first real error new devs hit is now actionable.
  • Bootstraps a CHANGELOG.md per SDK (Keep-a-Changelog) with packaging config so it actually ships from crates.io / npm / PyPI.
  • Adds a warn-only CI guard that (a) compiles the canonical Rust Hello World from a source-of-truth examples/readme_hello.rs and (b) probes iii.<method>() calls in Python READMEs against the real iii.III class — catching the exact iii.connect() shape of bug that py_compile misses.
  • Adds golden-good/golden-bad meta-fixtures so CI proves the guard itself works (not silently rotted).

This was produced after a /devex-review audit and /autoplan review; the audit found four P0 doc lies plus surface-level ergonomic gaps. Dual-voice consensus also surfaced a bigger reframe (generate READMEs from tested source) — approved as follow-up; this PR seeds the Rust foothold via examples/readme_hello.rs so that future PRs can extend the pattern.

Out of scope (deferred to follow-up): rewriting every error message, splitting 1–2k line SDK core files, full deprecation infrastructure, live playground / generated API reference.

Files touched

  • Rust SDK: src/protocol.rs, src/error.rs, tests/ergonomics.rs (new), examples/readme_hello.rs (new), README.md, CHANGELOG.md (new)
  • Python SDK: src/iii/iii.py, tests/test_init_api.py, README.md, pyproject.toml, CHANGELOG.md (new), uv.lock (version sync)
  • Node SDK: src/iii.ts, README.md, package.json, CHANGELOG.md (new)
  • Root: sdk/README.md
  • CI guard: .github/workflows/sdk-readme.yml (new), scripts/readme-guard/check_python_readme.py (new), sdk/fixtures/readme-guard/ (new)

Test plan

  • cargo check -p iii-sdk --example readme_hello — compiles
  • cargo test -p iii-sdk --test ergonomics — 7/7 pass (constructors + error-message contract)
  • cargo test -p iii-sdk --lib — 52/52 pass
  • cargo package --list in sdk/packages/rust/iii/ includes CHANGELOG.md and examples/readme_hello.rs
  • Python: pytest tests/test_init_api.py — 4/4 pass (includes new test_not_connected_error_is_actionable)
  • Python: python -m build --wheel produces a wheel containing iii/CHANGELOG.md
  • Python: Changelog URL renders on PyPI-style metadata
  • Node: pnpm --filter iii-sdk exec tsc --noEmit — clean
  • Node: pnpm --filter iii-sdk build — clean
  • Node: npm pack --dry-run — tarball includes dist/, README.md, CHANGELOG.md (41 files)
  • README guard meta-tests: golden-good exits 0, golden-bad exits 1 (caught iii.connect()), all three real READMEs exit 0
  • Reviewer: rebase onto current main if desired (branch was cut 1 commit behind; no conflicts expected since the intervening commit touches install.sh only)
  • Follow-up PR: flip continue-on-error: false on the two jobs in sdk-readme.yml once this lands green

Verification commands

cargo check -p iii-sdk --example readme_hello
cargo test -p iii-sdk --test ergonomics
(cd sdk/packages/python/iii && PYTHONPATH="$(pwd)/src" python -m pytest tests/test_init_api.py --no-cov -v)
pnpm --filter iii-sdk exec tsc --noEmit
PYTHONPATH="sdk/packages/python/iii/src" python scripts/readme-guard/check_python_readme.py \
  sdk/packages/python/iii/README.md \
  sdk/packages/node/iii/README.md \
  sdk/README.md

Summary by CodeRabbit

  • Documentation

    • Added Prerequisites section across all SDK READMEs requiring iii engine installation and startup before running examples.
    • Updated Hello World examples to reflect current API patterns.
    • Added CHANGELOG files for Python, Node, and Rust packages.
  • Bug Fixes

    • Improved error messages with actionable guidance, including engine startup commands and documentation links.

ytallo added 2 commits April 21, 2026 13:42
Rust:
- Add `TriggerRequest::new(function_id, payload)` with
  `.with_action(action)` / `.with_timeout_ms(ms)` chainable helpers.
  The root SDK README already documented `TriggerRequest::new` — this
  makes it real.
- Rewrite `IIIError::NotConnected` to include the engine URL, the
  `iii --config config.yaml` command, and a link to https://iii.dev/docs/install.
- New `tests/ergonomics.rs` unit tests for the constructors and the
  new error-message contract (7 tests, all green).

Python:
- `_wait_until_connected` raises `ConnectionError` with the target URL,
  how to start the engine, and the docs link.
- New test in `test_init_api.py` asserting the error contains each piece.

Node:
- Max-retries failure log names the URL, the start command, and the docs link.

No existing call site changes. Purely additive in Rust, string-only changes in
Python/Node. All existing SDK tests pass unchanged.
READMEs (Python, Rust, Root, Node):
- Python: remove non-existent `iii.connect()` call, switch Hello World
  to string-form `register_function("greet", handler)`, add Prerequisites
  block.
- Rust: bump install line from `iii-sdk = "0.3"` (lied about version) to
  `"0.11"`, rewrite Hello World with signatures that actually compile
  (`RegisterFunction::new_async`, `TriggerRequest::new`, no `?` on
  `register_worker`), promote the `IIITrigger::Http(...).for_function(...)`
  typed builder as the recommended trigger API, keep raw struct-literal
  form as an advanced escape hatch, add Prerequisites block.
- Root: rewrite Rust Hello World to match the per-SDK README (drop fake
  `TriggerRequest::new` without the impl), spell out `TriggerAction.Void()`
  for Python (previously marked "Same" as Node), add a shared
  Prerequisites block.
- Node: add Prerequisites block (snippets were already correct).

Source-of-truth Rust example:
- New `sdk/packages/rust/iii/examples/readme_hello.rs` — the canonical
  Rust Hello World, verified by CI via `cargo check --example readme_hello`.
  The README links to this file so drift is surface-visible.

CHANGELOGs + packaging:
- Bootstrap `CHANGELOG.md` per SDK in Keep-a-Changelog format with an
  [Unreleased] entry describing this PR and one retro entry for the
  `call`/`callVoid`/`triggerVoid` removal.
- Node `package.json`: add explicit `files` array including `CHANGELOG.md`.
- Python `pyproject.toml`: add `[tool.hatch.build.targets.wheel.force-include]`
  so the top-level `CHANGELOG.md` ships inside the wheel at
  `iii/CHANGELOG.md`, plus `[tool.hatch.build.targets.sdist]`
  include list and a `Changelog` project URL.
- Verified: `npm pack --dry-run`, `python -m build --wheel`, and
  `cargo package --list` all include CHANGELOG.md for their respective
  registries.

CI README guard (warn-only):
- New `.github/workflows/sdk-readme.yml` with two non-blocking jobs:
  Rust runs `cargo check --example readme_hello`; Python runs an
  attribute-probe that parses README code fences via `markdown-it-py`
  and verifies every `iii.<method>()` call resolves against the real
  `iii.III` class — catches the `iii.connect()` class of bug that
  `python -m py_compile` would miss.
- New `scripts/readme-guard/check_python_readme.py` — the attribute-probe.
- New meta-guard fixtures at `sdk/fixtures/readme-guard/` (golden-good
  and golden-bad) so a separate CI step proves the guard itself is
  working. Workflow runs warn-only for this PR; a follow-up flips
  `continue-on-error: false` once the three SDKs are green.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
iii-website Ready Ready Preview, Comment Apr 21, 2026 4:44pm
motia-docs Ready Ready Preview, Comment Apr 21, 2026 4:44pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

This PR establishes automated validation of SDK README examples and improves developer experience across Python, Node, and Rust SDKs through updated documentation, enhanced error messages, new builder APIs, and changelog files. A GitHub Actions workflow runs CI checks to ensure Rust and Python code examples in READMEs remain valid.

Changes

Cohort / File(s) Summary
CI/Testing Infrastructure
.github/workflows/sdk-readme.yml, scripts/readme-guard/check_python_readme.py, sdk/fixtures/readme-guard/*
New GitHub Actions workflow validates Rust and Python SDK README examples. Python guard script parses Markdown, extracts Python code blocks, and checks that all iii.<attribute>() calls match public III class members. Golden-good/golden-bad fixtures provide test cases for guard validation.
SDK Root Documentation
sdk/README.md
Adds Prerequisites section for engine setup. Updates Python example to use register_function("greet", ...) string signature. Rewrites Rust example with RegisterFunction::new_async, typed IIITrigger::Http(HttpTriggerConfig...), and TriggerRequest::new builders. Updates API table and adds CI sync verification note.
Python SDK Documentation & Configuration
sdk/packages/python/iii/README.md, sdk/packages/python/iii/CHANGELOG.md, sdk/packages/python/iii/pyproject.toml
Adds Prerequisites section and updates examples to use string-first register_function("greet", handler) syntax. Clarifies that register_worker() auto-connects. Introduces CHANGELOG with connection error improvements and docs URL. Updates pyproject.toml to include CHANGELOG.md in distributions and adds Changelog URL.
Python SDK Error Handling & Tests
sdk/packages/python/iii/src/iii/iii.py, sdk/packages/python/iii/tests/test_init_api.py
Enhanced _wait_until_connected() error messages with actionable guidance: includes engine address, CLI hints (iii --config), default WebSocket URL, and docs link. New test validates error message contains problem statement, cause, address, and remediation steps.
Node SDK Documentation & Configuration
sdk/packages/node/iii/README.md, sdk/packages/node/iii/CHANGELOG.md, sdk/packages/node/iii/package.json
Adds Prerequisites section describing engine startup and default WebSocket URL. Introduces CHANGELOG documenting connection message improvements, new Prerequisites docs, and removal of legacy call/callVoid/triggerVoid methods. Restricts published package files via files field.
Rust SDK Error Handling
sdk/packages/rust/iii/src/error.rs
Expands IIIError::NotConnected message with verification steps, engine startup guidance (iii --config), WebSocket URL reference, and docs link for enhanced actionability.
Rust SDK Builders & Constructors
sdk/packages/rust/iii/src/protocol.rs, sdk/packages/rust/iii/tests/ergonomics.rs
Adds TriggerRequest::new(function_id, payload) constructor and fluent setters with_action() and with_timeout_ms() for ergonomic trigger building. New ergonomics test validates constructor defaults, builder chaining, and error message formatting.
Rust SDK Example & Documentation
sdk/packages/rust/iii/examples/readme_hello.rs, sdk/packages/rust/iii/README.md, sdk/packages/rust/iii/CHANGELOG.md
Adds canonical readme_hello.rs example using RegisterFunction::new_async, typed IIITrigger::Http(HttpTriggerConfig...) triggers, and TriggerRequest::new builders verified by CI. README updates target SDK 0.11, adds Prerequisites, rewrites examples with new builder APIs, and notes Hello World sync/CI verification. CHANGELOG documents new APIs, example addition, and legacy method removal.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • iii-hq/skills#10: Documentation and examples changing Python/Rust register_function signatures and aligning cross-language examples directly address SDK audit findings around consistency and ergonomics.

Possibly related PRs

Suggested reviewers

  • sergiofilhowz
  • guibeira
  • anthonyiscoding

Hop, hop! 🐰 A guard to keep READMEs true,
With builders bright and examples new,
Error messages clear from docs to code,
The SDK path now easier to road! 📚✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing README documentation issues, making error messages actionable, and adding a compile-checked Hello World example.
Description check ✅ Passed The description covers what changed and why, but the Apache 2 licensing checkbox is not marked as required by the template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-sdk-dx-fixes

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
sdk/packages/python/iii/README.md (1)

25-48: ⚠️ Potential issue | 🟡 Minor

Hello World should end with iii.shutdown().

The referenced guideline for this file says the Hello World should "reflect the current trigger(..., action=TriggerAction.Void()) and iii.shutdown() usage". The current snippet never calls shutdown(), which leaves the background event-loop thread running (it's non-daemon — see self._thread = threading.Thread(target=self._loop.run_forever, daemon=False) in iii.py) and will hang a copy-pasted script on exit.

📝 Suggested addition
 result = iii.trigger({"function_id": "greet", "payload": {"name": "world"}})
 print(result)  # {"message": "Hello, world!"}
+
+iii.shutdown()

As per coding guidelines: "For fire-and-forget/shutdown, reflect the current trigger(..., action=TriggerAction.Void()) and iii.shutdown() usage."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/packages/python/iii/README.md` around lines 25 - 48, Update the example
to perform a fire-and-forget trigger and then cleanly stop the worker: call
trigger with action=TriggerAction.Void() (or equivalent) for the fire-and-forget
invocation and then call iii.shutdown() at the end to stop the background
event-loop thread created by register_worker; ensure the example imports/uses
TriggerAction and demonstrates the shutdown call after the trigger so a
copy-pasted script will not hang.
🧹 Nitpick comments (1)
sdk/packages/rust/iii/src/error.rs (1)

8-14: Carry the target URL in NotConnected for parity with Node/Python.

The Node (iii.ts:722) and Python (iii.py:156) implementations interpolate the actual engine address into the error message, which is critical when multiple services or environment-specific URLs are in play. The Rust implementation uses a unit variant and can only mention the default ws://localhost:49134, making the error misleading for users who passed a custom URL to register_worker(...).

Promote the variant to carry the address:

♻️ Proposed refactor
-    #[error(
-        "iii is not connected: engine unreachable. Verify the engine is running \
-         (`iii --config config.yaml`) and that the WebSocket URL passed to \
-         `register_worker` matches (default: ws://localhost:49134). \
-         See https://iii.dev/docs/install"
-    )]
-    NotConnected,
+    #[error(
+        "iii is not connected: engine unreachable at {address}. Verify the engine is running \
+         (`iii --config config.yaml`) and that the WebSocket URL passed to \
+         `register_worker` matches (default: ws://localhost:49134). \
+         See https://iii.dev/docs/install"
+    )]
+    NotConnected { address: String },

The address is available at all 5 construction sites (iii.rs:1152, iii.rs:1418, and 3 sites in channels.rs). The ergonomics test (tests/ergonomics.rs:9) will need a field assertion update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/packages/rust/iii/src/error.rs` around lines 8 - 14, Change the
NotConnected unit variant in the Error enum in error.rs to carry the engine
address (e.g., NotConnected(String)) and update its Display/#[error(...)]
message to interpolate that address instead of the hardcoded default; then
update all construction sites that create NotConnected (references in iii.rs at
the two locations and the three sites in channels.rs) to pass the actual address
String, and adjust the ergonomics test in tests/ergonomics.rs to assert the new
field or updated message accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/sdk-readme.yml:
- Around line 6-25: Update the path filters in .github/workflows/sdk-readme.yml
so the workflow actually triggers when Node SDK sources change: add the Node
package paths (for example include 'sdk/packages/node/iii/src/**' and
'sdk/packages/node/iii/README.md' or broader 'sdk/packages/node/**') to both the
push and pull_request path lists so the guard step that probes
sdk/packages/node/iii/README.md and sdk/README.md runs when Node API-source
files are modified.
- Around line 84-89: The workflow step invoking check_python_readme.py is
incorrectly being run against the Node README (sdk/packages/node/iii/README.md),
which is a no-op because check_python_readme.py parses Python fenced blocks;
remove sdk/packages/node/iii/README.md from the arguments to the
check_python_readme.py invocation OR add a separate Node-specific guard:
create/run a Node/TS checker (e.g., check_node_readme.js or a tsc/ts-node
type-check command) against sdk/packages/node/iii/README.md and update the
workflow step to call that checker instead of passing the Node README into
check_python_readme.py.

In `@scripts/readme-guard/check_python_readme.py`:
- Around line 68-73: The parser currently swallows SyntaxError by returning []
after ast.parse(source) fails; change this so a SyntaxError in README Python
fences is treated as a test failure — raise or append a descriptive error
(include the snippet context) instead of returning an empty list in the
try/except around ast.parse in the function that parses fences; additionally,
wrap the calls to attr_calls_on_iii_var inside check_file (the block around the
lines currently invoking attr_calls_on_iii_var) in a try/except that catches
SyntaxError and reports it (rather than skipping), so malformed Python snippets
are reported as errors with clear context.

In `@sdk/packages/python/iii/CHANGELOG.md`:
- Around line 25-30: The changelog entry uses camelCase method names; update it
to Python-style snake_case: replace `call`, `callVoid`, `triggerVoid` with
`call`, `call_void`, `trigger_void` in the Removed section and ensure the usage
example references `iii.trigger()` and the Python `TriggerAction.Void()`
invocation; adjust the text so Python developers will see the correct snake_case
symbols (`call`, `call_void`, `trigger_void`, `trigger`, `TriggerAction.Void`)
when searching the codebase.

In `@sdk/packages/rust/iii/README.md`:
- Line 87: Examples use dotted function IDs; update them to use :: namespace
separators for consistency. Locate calls to iii.register_function and uses of
RegisterFunction::new_async (e.g., RegisterFunction::new_async("orders.create",
...)) and change string IDs from "orders.create" style to "orders::create" (and
any other dotted IDs in the README examples) so they match the :: convention
used elsewhere (e.g., stream::set/stream::update).

In `@sdk/README.md`:
- Around line 111-112: Update the cross-SDK API table to reflect that Python's
trigger() is synchronous (remove the leading "await" in the Python column for
the Invoke rows) and change any "Node/Python" example that currently uses JS
object literal syntax to use Python dict syntax instead (e.g., use
{"function_id": ..., "payload": ...} style for Python rather than JS object
notation); specifically edit the entries referencing iii.trigger(),
TriggerAction.Void(), and TriggerRequest examples so Python examples omit await
and use Python dicts where Node/Python is indicated.

---

Outside diff comments:
In `@sdk/packages/python/iii/README.md`:
- Around line 25-48: Update the example to perform a fire-and-forget trigger and
then cleanly stop the worker: call trigger with action=TriggerAction.Void() (or
equivalent) for the fire-and-forget invocation and then call iii.shutdown() at
the end to stop the background event-loop thread created by register_worker;
ensure the example imports/uses TriggerAction and demonstrates the shutdown call
after the trigger so a copy-pasted script will not hang.

---

Nitpick comments:
In `@sdk/packages/rust/iii/src/error.rs`:
- Around line 8-14: Change the NotConnected unit variant in the Error enum in
error.rs to carry the engine address (e.g., NotConnected(String)) and update its
Display/#[error(...)] message to interpolate that address instead of the
hardcoded default; then update all construction sites that create NotConnected
(references in iii.rs at the two locations and the three sites in channels.rs)
to pass the actual address String, and adjust the ergonomics test in
tests/ergonomics.rs to assert the new field or updated message accordingly.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 52b9c046-da35-4bab-a198-9c686aa266df

📥 Commits

Reviewing files that changed from the base of the PR and between f56b7b0 and e073b99.

⛔ Files ignored due to path filters (1)
  • sdk/packages/python/iii/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • .github/workflows/sdk-readme.yml
  • scripts/readme-guard/check_python_readme.py
  • sdk/README.md
  • sdk/fixtures/readme-guard/golden-bad-python.md
  • sdk/fixtures/readme-guard/golden-good-python.md
  • sdk/packages/node/iii/CHANGELOG.md
  • sdk/packages/node/iii/README.md
  • sdk/packages/node/iii/package.json
  • sdk/packages/node/iii/src/iii.ts
  • sdk/packages/python/iii/CHANGELOG.md
  • sdk/packages/python/iii/README.md
  • sdk/packages/python/iii/pyproject.toml
  • sdk/packages/python/iii/src/iii/iii.py
  • sdk/packages/python/iii/tests/test_init_api.py
  • sdk/packages/rust/iii/CHANGELOG.md
  • sdk/packages/rust/iii/README.md
  • sdk/packages/rust/iii/examples/readme_hello.rs
  • sdk/packages/rust/iii/src/error.rs
  • sdk/packages/rust/iii/src/protocol.rs
  • sdk/packages/rust/iii/tests/ergonomics.rs

Comment on lines +6 to +25
paths:
- 'sdk/**/README.md'
- 'sdk/README.md'
- 'sdk/packages/rust/iii/examples/readme_hello.rs'
- 'sdk/packages/rust/iii/src/**'
- 'sdk/packages/python/iii/src/**'
- 'sdk/fixtures/readme-guard/**'
- 'scripts/readme-guard/**'
- '.github/workflows/sdk-readme.yml'
pull_request:
branches: [main]
paths:
- 'sdk/**/README.md'
- 'sdk/README.md'
- 'sdk/packages/rust/iii/examples/readme_hello.rs'
- 'sdk/packages/rust/iii/src/**'
- 'sdk/packages/python/iii/src/**'
- 'sdk/fixtures/readme-guard/**'
- 'scripts/readme-guard/**'
- '.github/workflows/sdk-readme.yml'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Path filters omit Node/root SDK sources that the guard probes.

The final guard step probes sdk/packages/node/iii/README.md and sdk/README.md, but the triggers do not include sdk/packages/node/iii/src/** or anything that would catch Node API-surface churn. If a Node public API renames, the Node README could regress without this workflow running. Either narrow the guard targets (see sibling comment) or expand the path filters to match what you're actually checking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sdk-readme.yml around lines 6 - 25, Update the path
filters in .github/workflows/sdk-readme.yml so the workflow actually triggers
when Node SDK sources change: add the Node package paths (for example include
'sdk/packages/node/iii/src/**' and 'sdk/packages/node/iii/README.md' or broader
'sdk/packages/node/**') to both the push and pull_request path lists so the
guard step that probes sdk/packages/node/iii/README.md and sdk/README.md runs
when Node API-source files are modified.

Comment on lines +84 to +89
- name: Run guard against real READMEs
run: |
python scripts/readme-guard/check_python_readme.py \
sdk/packages/python/iii/README.md \
sdk/packages/node/iii/README.md \
sdk/README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Probing the Node README with the Python attribute probe is a no-op.

check_python_readme.py parses Python fenced blocks and checks iii.<attr>(...) against iii.iii.III. The Node README contains TypeScript/JS code blocks, so this step will almost certainly find nothing and exit 0 regardless of drift — it provides false confidence. Either drop sdk/packages/node/iii/README.md from this invocation, or add a separate Node guard (e.g., a TS type-check of the Node Hello World) and run that against the Node README.

📝 Minimal fix
       - name: Run guard against real READMEs
         run: |
           python scripts/readme-guard/check_python_readme.py \
             sdk/packages/python/iii/README.md \
-            sdk/packages/node/iii/README.md \
             sdk/README.md
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Run guard against real READMEs
run: |
python scripts/readme-guard/check_python_readme.py \
sdk/packages/python/iii/README.md \
sdk/packages/node/iii/README.md \
sdk/README.md
- name: Run guard against real READMEs
run: |
python scripts/readme-guard/check_python_readme.py \
sdk/packages/python/iii/README.md \
sdk/README.md
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sdk-readme.yml around lines 84 - 89, The workflow step
invoking check_python_readme.py is incorrectly being run against the Node README
(sdk/packages/node/iii/README.md), which is a no-op because
check_python_readme.py parses Python fenced blocks; remove
sdk/packages/node/iii/README.md from the arguments to the check_python_readme.py
invocation OR add a separate Node-specific guard: create/run a Node/TS checker
(e.g., check_node_readme.js or a tsc/ts-node type-check command) against
sdk/packages/node/iii/README.md and update the workflow step to call that
checker instead of passing the Node README into check_python_readme.py.

Comment on lines +68 to +73
try:
tree = ast.parse(source)
except SyntaxError:
# Syntax errors are a separate class of problem; `py_compile` already
# catches them. Skip attribute checking when parse fails.
return []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether syntax errors are suppressed here and whether another workflow step explicitly compiles README fences.
rg -n -C4 'except SyntaxError|return \[\]' scripts/readme-guard/check_python_readme.py
rg -n -C3 'py_compile|compileall|check_python_readme' --glob '*.yml' --glob '*.yaml'

Repository: iii-hq/iii

Length of output: 493


🏁 Script executed:

#!/bin/bash
# Search for py_compile and compileall usage throughout the codebase
echo "=== Searching for py_compile usage ==="
rg -r 'py_compile' --no-heading

echo ""
echo "=== Searching for compileall usage ==="
rg -r 'compileall' --no-heading

echo ""
echo "=== Searching for check_python_readme invocation in all files ==="
rg -r 'check_python_readme' --no-heading

Repository: iii-hq/iii

Length of output: 1407


🏁 Script executed:

#!/bin/bash
# View the full context of the check_file function and lines 93-94
echo "=== Full check_python_readme.py file ==="
wc -l scripts/readme-guard/check_python_readme.py
echo ""
cat -n scripts/readme-guard/check_python_readme.py

Repository: iii-hq/iii

Length of output: 5648


🏁 Script executed:

#!/bin/bash
# Check for workflow files and their content
echo "=== Workflow files ==="
fd -e yml -e yaml | head -20

echo ""
echo "=== Check for any CI/workflow references to readme or syntax ==="
rg -l 'readme|syntax' --glob '*.yml' --glob '*.yaml' --glob '.github'

Repository: iii-hq/iii

Length of output: 912


🏁 Script executed:

#!/bin/bash
cat -n .github/workflows/sdk-readme.yml

Repository: iii-hq/iii

Length of output: 3619


🏁 Script executed:

#!/bin/bash
cat -n sdk/fixtures/readme-guard/golden-bad-python.md

Repository: iii-hq/iii

Length of output: 655


Fail closed on Python syntax errors in README fences.

Right now a malformed Python snippet returns [] instead of reporting an error, allowing this guard to exit successfully when it should fail. The comment claiming py_compile already catches syntax errors is incorrect—the workflow contains no such compile step.

Lines 93–94 in check_file also need wrapping to catch and report SyntaxError from attr_calls_on_iii_var instead of silently skipping syntax-invalid blocks.

Proposed fix
-    try:
-        tree = ast.parse(source)
-    except SyntaxError:
-        # Syntax errors are a separate class of problem; `py_compile` already
-        # catches them. Skip attribute checking when parse fails.
-        return []
+    tree = ast.parse(source)
@@
     md_text = path.read_text()
     for block_line, source in extract_python_fences(md_text):
-        for call_lineno, attr in attr_calls_on_iii_var(source):
+        try:
+            calls = attr_calls_on_iii_var(source)
+        except SyntaxError as exc:
+            errors.append(
+                f"{path}:{block_line + (exc.lineno or 1) - 1}: "
+                f"Python README snippet has invalid syntax: {exc.msg}"
+            )
+            continue
+
+        for call_lineno, attr in calls:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/readme-guard/check_python_readme.py` around lines 68 - 73, The parser
currently swallows SyntaxError by returning [] after ast.parse(source) fails;
change this so a SyntaxError in README Python fences is treated as a test
failure — raise or append a descriptive error (include the snippet context)
instead of returning an empty list in the try/except around ast.parse in the
function that parses fences; additionally, wrap the calls to
attr_calls_on_iii_var inside check_file (the block around the lines currently
invoking attr_calls_on_iii_var) in a try/except that catches SyntaxError and
reports it (rather than skipping), so malformed Python snippets are reported as
errors with clear context.

Comment on lines +25 to +30
### Removed
- Removed legacy `call`, `callVoid`, `triggerVoid` methods. Use `trigger()`
for all invocations:
```python
iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any historical Python definitions/usages of the legacy invocation methods.
rg -nP --type=py -C2 '\b(call_void|trigger_void|callVoid|triggerVoid)\b'
git log --all --oneline -S 'call_void' -- 'sdk/packages/python' | head
git log --all --oneline -S 'trigger_void' -- 'sdk/packages/python' | head

Repository: iii-hq/iii

Length of output: 773


🏁 Script executed:

#!/bin/bash
# First, read the CHANGELOG.md to see full context around lines 25-30
cat -n sdk/packages/python/iii/CHANGELOG.md | head -40

Repository: iii-hq/iii

Length of output: 1577


🏁 Script executed:

#!/bin/bash
# Check the commit that mentions unifying Python SDK public API
git show f1440898 --stat | head -20

Repository: iii-hq/iii

Length of output: 1125


🏁 Script executed:

#!/bin/bash
# Look at the diff for Python SDK changes in that commit, focusing on removed methods
git show f1440898 -- sdk/packages/python/ | grep -A5 -B5 'call\|trigger' | head -100

Repository: iii-hq/iii

Length of output: 4611


🏁 Script executed:

#!/bin/bash
# Check the parent commit before the refactor to see the old Python SDK API
git show f1440898~1:sdk/packages/python/iii/src/iii/__init__.py 2>/dev/null | head -100

Repository: iii-hq/iii

Length of output: 2107


🏁 Script executed:

#!/bin/bash
# Also check if there's an older version with these methods by looking at the SDK structure
git log --all --oneline --follow -- 'sdk/packages/python/iii/src/iii/__init__.py' | head -20

Repository: iii-hq/iii

Length of output: 1121


🏁 Script executed:

#!/bin/bash
# Search for any mention of 'call' method in Python SDK history
git log --all -p -- 'sdk/packages/python' | grep -B5 -A5 'def call' | head -60

Repository: iii-hq/iii

Length of output: 2117


Correct CHANGELOG to use Python-style method names.

The Python SDK removed call, call_void (not callVoid), and trigger_void (not triggerVoid). Python developers will not find camelCase names when grepping their codebase. Update the entry:

- Removed legacy `call`, `call_void`, `trigger_void` methods. Use `trigger()`
  for all invocations:
  ```python
  iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @sdk/packages/python/iii/CHANGELOG.md around lines 25 - 30, The changelog
entry uses camelCase method names; update it to Python-style snake_case: replace
call, callVoid, triggerVoid with call, call_void, trigger_void in
the Removed section and ensure the usage example references iii.trigger() and
the Python TriggerAction.Void() invocation; adjust the text so Python
developers will see the correct snake_case symbols (call, call_void,
trigger_void, trigger, TriggerAction.Void) when searching the codebase.


</details>

<!-- fingerprinting:phantom:poseidon:nectarine:1db9236b-3c5d-439f-9488-ed2fa9699b73 -->

<!-- This is an auto-generated comment by CodeRabbit -->

use serde_json::{Value, json};

iii.register_function("orders.create", |input: Value| async move {
iii.register_function(RegisterFunction::new_async("orders.create", |input: Value| async move {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use :: separators for namespaced function IDs in examples.

These snippets use dotted IDs while the same README already uses stream::set/stream::update; keeping examples on :: avoids teaching two ID conventions.

Proposed fix
-iii.register_function(RegisterFunction::new_async("orders.create", |input: Value| async move {
+iii.register_function(RegisterFunction::new_async("orders::create", |input: Value| async move {
@@
-        .for_function("orders.create"),
+        .for_function("orders::create"),
@@
-    function_id: "orders.create".into(),
+    function_id: "orders::create".into(),
@@
-    .trigger(TriggerRequest::new("orders.create", json!({ "body": { "item": "widget" } })))
+    .trigger(TriggerRequest::new("orders::create", json!({ "body": { "item": "widget" } })))
@@
-    TriggerRequest::new("analytics.track", json!({ "event": "page_view" }))
+    TriggerRequest::new("analytics::track", json!({ "event": "page_view" }))
@@
-    TriggerRequest::new("orders.process", json!({ "order_id": "456" }))
+    TriggerRequest::new("orders::process", json!({ "order_id": "456" }))

As per coding guidelines, use :: separator for function IDs.

Also applies to: 103-105, 118-118, 132-145

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/packages/rust/iii/README.md` at line 87, Examples use dotted function
IDs; update them to use :: namespace separators for consistency. Locate calls to
iii.register_function and uses of RegisterFunction::new_async (e.g.,
RegisterFunction::new_async("orders.create", ...)) and change string IDs from
"orders.create" style to "orders::create" (and any other dotted IDs in the
README examples) so they match the :: convention used elsewhere (e.g.,
stream::set/stream::update).

Comment thread sdk/README.md
Comment on lines +111 to +112
| Invoke (await) | `await iii.trigger({ function_id, payload })` | `await iii.trigger({"function_id": id, "payload": data})` | `iii.trigger(TriggerRequest::new(id, payload)).await?` | Invoke a function and wait for the result |
| Invoke (fire-and-forget) | `iii.trigger({ function_id, payload, action: TriggerAction.Void() })` | `iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})` | `iii.trigger(TriggerRequest::new(id, payload).with_action(TriggerAction::Void)).await?` | Invoke without waiting |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether Python `III.trigger` is synchronous and locate README guidance that still says to await it.
rg -n -C3 '^\s*(async\s+)?def\s+trigger\s*\(' --glob 'iii.py'
rg -n -C2 'await\s+iii\.trigger|Node/Python|TriggerAction\.Void\(\)' --glob 'README.md'

Repository: iii-hq/iii

Length of output: 7795


Fix Python trigger() invocation and removed-method note in cross-SDK API table

Line 111 incorrectly shows await iii.trigger(...) for Python, but Python's trigger() is synchronous. Additionally, line 120's removed-method note uses JS object syntax for "Node/Python" when Python requires dict syntax.

Proposed fix
-| Invoke (await)           | `await iii.trigger({ function_id, payload })`                 | `await iii.trigger({"function_id": id, "payload": data})`                        | `iii.trigger(TriggerRequest::new(id, payload)).await?`                                         | Invoke a function and wait for the result              |
+| Invoke (wait for result) | `await iii.trigger({ function_id, payload })`                 | `iii.trigger({"function_id": id, "payload": data})`                              | `iii.trigger(TriggerRequest::new(id, payload)).await?`                                         | Invoke a function and wait for the result              |
@@
-> `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `trigger({ function_id, payload, action: TriggerAction.Void() })` (Node/Python) or `TriggerRequest::new(id, payload).with_action(TriggerAction::Void)` (Rust).
+> `call`, `callVoid`, `triggerVoid` (and Python/Rust equivalents) have been removed. Use `trigger()` for all invocations. For fire-and-forget, use `iii.trigger({ function_id, payload, action: TriggerAction.Void() })` in Node, `iii.trigger({"function_id": id, "payload": data, "action": TriggerAction.Void()})` in Python, or `TriggerRequest::new(id, payload).with_action(TriggerAction::Void)` in Rust.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk/README.md` around lines 111 - 112, Update the cross-SDK API table to
reflect that Python's trigger() is synchronous (remove the leading "await" in
the Python column for the Invoke rows) and change any "Node/Python" example that
currently uses JS object literal syntax to use Python dict syntax instead (e.g.,
use {"function_id": ..., "payload": ...} style for Python rather than JS object
notation); specifically edit the entries referencing iii.trigger(),
TriggerAction.Void(), and TriggerRequest examples so Python examples omit await
and use Python dicts where Node/Python is indicated.

@ytallo
Copy link
Copy Markdown
Contributor Author

ytallo commented Apr 21, 2026

Superseded by #1523 (branch renamed from worktree-sdk-dx-fixes to sdk-dx-fixes and rustfmt fixed).

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.

1 participant