Skip to content

feat: turn android host shell into real swarm node host - #3

Merged
codeh007 merged 4 commits into
mainfrom
feat/real-swarm-node-host
Mar 27, 2026
Merged

codeh007 merged 4 commits into
mainfrom
feat/real-swarm-node-host

Conversation

@codeh007

@codeh007 codeh007 commented Mar 27, 2026 •

Copy link
Copy Markdown
Owner

Summary\n- replace the old host-shell probing flow with a real node runtime host UI\n- align the Android app with startNode/stopNode and real discovered peers\n- pin gomtm swarm AAR consumption in CI/release with metadata validation\n\n## Notes\n- CI/release requires pinned gomtm swarm AAR repo variables to be set\n- validation remains GitHub Actions based; no local Android build is treated as acceptance\n

Summary by CodeRabbit

  • New Features

    • Start/stop controls with persisted bootstrap and node name; UI shows state, bridge, peer ID, connected bootstrap, discovered peers list, and runtime logs.
  • Chores

    • App version bumped to 0.3.0.
    • CI/release now requires a pinned AAR contract, always downloads artifacts, validates SHA256/metadata, and records provenance with releases.
  • Docs

    • README and docs updated to reflect the pinned-AAR runtime contract and release provenance.
  • Permissions

    • App now requests network permissions.
  • Tests

    • Updated and added tests for runtime probing and peers parsing.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026 •

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02dbcb92-e1aa-44d5-920e-f05dcf55a910

📥 Commits

Reviewing files that changed from the base of the PR and between 161ac55 and f0afeaf.

📒 Files selected for processing (1)
  • app/build.gradle.kts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/build.gradle.kts

📝 Walkthrough

Walkthrough

The PR replaces optional/reflection-based AAR integration with a pinned AAR contract: CI/release workflows now require, download (with auth), verify SHA256 and metadata for gomtm-swarm-android.aar/JSON; the app gains explicit start/stop node APIs, UI inputs, runtime models for peers/config, updated tests, and docs.

Changes

Cohort / File(s) Summary
CI/Release Workflows
/.github/workflows/ci.yml, /.github/workflows/release.yml
Added pinned-AAR workflow inputs (gomtm_swarm_metadata_url, gomtm_swarm_aar_version, gomtm_swarm_aar_sha256), new tokens (GH_FALLBACK_TOKEN, GOMTM_RELEASE_TOKEN), enforce contract presence, always download AAR+metadata with Authorization, verify AAR SHA256 and JSON fields, produce provenance JSON, and bumped release tag to v0.3.
Build & Local AAR Guidance
app/build.gradle.kts, app/libs/README.md
Bumped app version 0.2.0 → 0.3.0; replaced generic fileTree AAR inclusion with explicit libs/gomtm-swarm-android.aar. README updated to document CI-driven pinned AAR download and provenance.
Core Runtime & Status
app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt, app/src/main/java/com/gomtm/android/swarm/SwarmStatus.kt
SwarmRuntime now uses single bridgeClassName + configClassName, adds reflective start(context, config) and stop(), updates probe() to call lowercased bridge methods and populate discoveredPeers/rawDiscoveredPeers. SwarmStatus simplified to non-nullable fields, added discovery fields, and missing() now accepts a message.
New Runtime Models
app/src/main/java/com/gomtm/android/swarm/SwarmNodeConfig.kt, app/src/main/java/com/gomtm/android/swarm/DiscoveredPeer.kt, app/src/main/java/com/gomtm/android/swarm/SwarmUiModel.kt
Added SwarmNodeConfig (bootstrap, nodeName, autoReconnect + name normalization), DiscoveredPeer with parseSnapshot(raw) to parse peers JSON, and SwarmUiModel with factory to derive display bootstrap/error strings.
UI, Manifest & Layout
app/src/main/java/com/gomtm/android/MainActivity.kt, app/src/main/res/layout/activity_main.xml, app/src/main/res/values/strings.xml, app/src/main/AndroidManifest.xml
MainActivity adds editable bootstrap/node-name inputs persisted to prefs, start/stop/refresh buttons, centralized action error handling, and renders bridge/state/peer/bootstrap/error and discovered peers. Layout and strings updated for node-control flow. Added INTERNET and ACCESS_NETWORK_STATE permissions.
Tests
app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt, app/src/test/java/com/gomtm/android/swarm/SwarmUiModelTest.kt
Updated runtime tests for new bridge/config naming and error semantics, added discovered-peers parsing assertions, and added SwarmUiModel tests covering bootstrap/error fallback behaviors.
Documentation & Roadmap
README.md, docs/roadmap.md, docs/swarm-runtime-integration.md
README recharacterizes the app as a swarm node consumer with explicit start/stop APIs. Roadmap and integration doc updated to require CI-driven pinned AAR assets, metadata, checksum validation, provenance recording, and a stricter UI contract.

Sequence Diagrams

sequenceDiagram
    participant User
    participant MainActivity
    participant SharedPrefs
    participant SwarmRuntime
    participant NativeBridge
    participant SwarmNode
    participant PeerNetwork

    User->>MainActivity: Enter bootstrap & node name
    MainActivity->>SharedPrefs: Save inputs
    User->>MainActivity: Click start
    MainActivity->>SwarmRuntime: start(context, config)
    SwarmRuntime->>NativeBridge: Reflectively instantiate config
    SwarmRuntime->>NativeBridge: Set bootstrap, node name, autoReconnect
    SwarmRuntime->>NativeBridge: Call startNode()
    NativeBridge->>SwarmNode: Initialize node runtime
    SwarmNode->>PeerNetwork: Connect and register
    SwarmRuntime-->>MainActivity: Return control
    User->>MainActivity: Click refresh
    MainActivity->>SwarmRuntime: probe()
    SwarmRuntime->>NativeBridge: Call getState(), getPeerID(), getBootstrapAddr(), getLastError()
    SwarmRuntime->>NativeBridge: Call getDiscoveredPeers()
    NativeBridge->>SwarmNode: Query runtime state & peers
    SwarmRuntime-->>MainActivity: Return SwarmStatus with peers list
    MainActivity->>MainActivity: Parse discoveredPeers, render UI
Loading
sequenceDiagram
    participant CI as GitHub Actions
    participant Storage as Artifact Storage
    participant Validator as Validation Logic
    participant Gradle
    participant APK

    CI->>Storage: Download gomtm-swarm-android.aar
    CI->>Storage: Download gomtm-swarm-android.json
    CI->>Validator: Verify AAR SHA256 matches pinned
    Validator-->>Validator: Parse JSON metadata
    Validator-->>Validator: Assert artifact, version, sha256 match
    alt Validation fails
        Validator-->>CI: Fail job
    else Validation passes
        Validator-->>CI: Proceed
        CI->>Gradle: Run build with AAR in app/libs
        Gradle->>APK: Link AAR, create APK
        APK-->>CI: APK + provenance JSON
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: add swarm-first public host shell #2: Prior PR that introduced the reflection-based host-shell and optional AAR probing; this change replaces that architecture with a pinned-AAR contract and explicit runtime start/stop APIs across CI, runtime classes, UI, and tests.

Poem

🐰
I hopped in code and found a key,
A pinned AAR to set nodes free.
Start and stop, the peers all play,
Provenance dancing on display.
Hooray — the swarm hops out today! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: turn android host shell into real swarm node host' directly and clearly summarizes the main transformation across the changeset: migrating from a passive host-shell probing architecture to an active swarm node runtime host with start/stop control and real peer discovery.

✏️ 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 feat/real-swarm-node-host

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt (1)

41-44: Deduplicate the discovered-peers JSON fixture to reduce drift risk.

The same payload appears in multiple places; extracting a shared test constant/helper would make updates safer.

Also applies to: 80-83

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

In `@app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt` around lines
41 - 44, Extract the repeated discovered-peers JSON into a single reusable test
fixture and replace the inline `json` literals in `SwarmRuntimeTest` (and the
duplicate at lines ~80-83) with that constant; for example, add a
`DISCOVERED_PEERS_JSON` constant in `SwarmRuntimeTest`'s companion object or a
shared `TestFixtures` object and update the tests that currently define `val
json = """..."""` to reference that constant so the payload is maintained in one
place.
app/src/main/java/com/gomtm/android/swarm/SwarmNodeConfig.kt (1)

12-12: Consider multi-bootstrap defaults for better cold-start resilience.

Line 12 uses a single default bootstrap endpoint; a small fallback list (or DNS-backed endpoint) would reduce startup fragility if that peer is unavailable.

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

In `@app/src/main/java/com/gomtm/android/swarm/SwarmNodeConfig.kt` at line 12,
SwarmNodeConfig currently exposes a single DEFAULT_BOOTSTRAP constant which is
fragile; change it to a small ordered list/array (e.g., DEFAULT_BOOTSTRAPS or
BOOTSTRAP_ENDPOINTS) containing multiple bootstrap multiaddrs (or a single
DNS-backed multiaddr) and update any code that reads DEFAULT_BOOTSTRAP to
iterate the list and try endpoints in order (or resolve the DNS endpoint) until
one connects; modify references to DEFAULT_BOOTSTRAP in SwarmNodeConfig and any
consumer code to use the new collection and implement a simple
fallback/selection strategy (try each address, skip unreachable ones) so
cold-starts are resilient.
app/src/main/res/layout/activity_main.xml (1)

27-55: Associate the visible labels with their EditTexts.

These TextViews are acting as persistent form labels. Without android:labelFor, screen readers have to rely on the temporary hints instead of the visible label once focus moves through the form.

Suggested fix
-        <TextView
+        <TextView
+            android:id="@+id/bootstrapInputLabel"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="20dp"
+            android:labelFor="@id/bootstrapInput"
             android:text="@string/bootstrap_input_label"
             android:textStyle="bold" />
@@
-        <TextView
+        <TextView
+            android:id="@+id/nodeNameInputLabel"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="12dp"
+            android:labelFor="@id/nodeNameInput"
             android:text="@string/node_name_input_label"
             android:textStyle="bold" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/layout/activity_main.xml` around lines 27 - 55, The two
TextView labels are not associated with their EditText fields, so add
android:labelFor attributes on the corresponding TextView elements to point to
the EditText IDs (use android:labelFor="@id/bootstrapInput" for the bootstrap
label and android:labelFor="@id/nodeNameInput" for the node name label) so
screen readers announce the persistent label instead of relying only on hints;
update the TextView attributes in the layout for the elements that precede the
EditText with IDs bootstrapInput and nodeNameInput.
🤖 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/release.yml:
- Around line 117-119: The workflow uploads the unsigned artifact
app-release-unsigned.apk; fix by adding a signing configuration in app module
build.gradle.kts (create a signingConfigs block and reference it from the
release buildType so release.buildType uses signingConfig release) and provide
keystore credentials via secure properties/ENV (so the Gradle release variant
produces a signed APK), then update the GitHub Actions workflow to publish the
signed output (e.g., the release APK path produced by the signed release variant
such as app/build/outputs/apk/release/app-release.apk) instead of
app-release-unsigned.apk. Ensure you reference the release buildType and
signingConfigs identifiers when making the changes.

In `@app/src/main/java/com/gomtm/android/MainActivity.kt`:
- Around line 23-50: The UI handlers and runAction currently call heavy/blocking
methods (swarmRuntime.start, swarmRuntime.stop, probe, drainLogs invoked via
start(), stop(), probe(), drainLogs()) on the main thread; move these calls into
a background coroutine/dispatcher (e.g., Dispatchers.IO or a dedicated
single-thread context) so reflection, file I/O and JNI round-trips run off the
UI thread, capture exceptions there, then post only the results/state back to
the main thread to update actionError and call render(); update runAction to
launch the background task and use withContext(Dispatchers.Main) or
runOnUiThread to set actionError and invoke render() so saveInputs(), render(),
and all UI updates remain on the main thread while start/stop/probe/drainLogs
execute off it.

In `@app/src/main/java/com/gomtm/android/swarm/DiscoveredPeer.kt`:
- Around line 26-35: The peer-parsing loop currently constructs DiscoveredPeer
objects even when peer_id is empty (optString returns ""), causing blank peers
to render; update the parsing in the block that reads from peers (the code
creating DiscoveredPeer instances) to first extract peerId =
item.optString("peer_id") and skip the entry if peerId is blank (continue)
before calling add(DiscoveredPeer(...)), ensuring DiscoveredPeer(peerId =
peerId, ...) is only created for non-empty peer_id values.

In `@app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt`:
- Around line 66-70: invokeString currently only catches
ReflectiveOperationException so IllegalStateException thrown by invokeStatic
(when bridge getters like getState or getDiscoveredPeers throw) bubbles up and
crashes probe() and drainLogs(); update invokeString to also catch
IllegalStateException and return an empty string on that exception, and add a
regression test that stubs a bridge getter to throw IllegalStateException and
asserts probe() and drainLogs() return safe defaults (empty string or expected
defaults) to ensure graceful degradation; target the invokeString, invokeStatic,
probe, drainLogs, getState and getDiscoveredPeers symbols when making changes
and adding the test.
- Around line 60-63: The runtimeBaseDir function currently ignores File.mkdirs()
failures; update runtimeBaseDir(context: Context) to check the mkdirs() return
value and also verify that the resulting File is a directory (File.isDirectory),
and if either check fails throw an informative exception (e.g., IOException or
IllegalStateException) so startNode() never receives a non-existent path; also
add a unit/instrumentation regression test that simulates filesDir/gomtm being
non-creatable (or an existing file) and asserts that runtimeBaseDir throws the
expected exception instead of allowing later bridge startup errors.

In `@app/src/main/java/com/gomtm/android/swarm/SwarmUiModel.kt`:
- Around line 8-16: The UI model currently uses hardcoded user-facing strings
VALUE_NOT_AVAILABLE and VALUE_NONE inside SwarmUiModel.from; remove those
English-only constants and change the API so the caller provides localized
fallback strings instead (e.g., add parameters to SwarmUiModel.from such as
notAvailableFallback and noneFallback or accept a single resource-derived pair)
and use those values when computing connectedBootstrap and errorText; update any
call sites that construct SwarmUiModel.from to pass getString(...) resource
values so localization is preserved.

---

Nitpick comments:
In `@app/src/main/java/com/gomtm/android/swarm/SwarmNodeConfig.kt`:
- Line 12: SwarmNodeConfig currently exposes a single DEFAULT_BOOTSTRAP constant
which is fragile; change it to a small ordered list/array (e.g.,
DEFAULT_BOOTSTRAPS or BOOTSTRAP_ENDPOINTS) containing multiple bootstrap
multiaddrs (or a single DNS-backed multiaddr) and update any code that reads
DEFAULT_BOOTSTRAP to iterate the list and try endpoints in order (or resolve the
DNS endpoint) until one connects; modify references to DEFAULT_BOOTSTRAP in
SwarmNodeConfig and any consumer code to use the new collection and implement a
simple fallback/selection strategy (try each address, skip unreachable ones) so
cold-starts are resilient.

In `@app/src/main/res/layout/activity_main.xml`:
- Around line 27-55: The two TextView labels are not associated with their
EditText fields, so add android:labelFor attributes on the corresponding
TextView elements to point to the EditText IDs (use
android:labelFor="@id/bootstrapInput" for the bootstrap label and
android:labelFor="@id/nodeNameInput" for the node name label) so screen readers
announce the persistent label instead of relying only on hints; update the
TextView attributes in the layout for the elements that precede the EditText
with IDs bootstrapInput and nodeNameInput.

In `@app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt`:
- Around line 41-44: Extract the repeated discovered-peers JSON into a single
reusable test fixture and replace the inline `json` literals in
`SwarmRuntimeTest` (and the duplicate at lines ~80-83) with that constant; for
example, add a `DISCOVERED_PEERS_JSON` constant in `SwarmRuntimeTest`'s
companion object or a shared `TestFixtures` object and update the tests that
currently define `val json = """..."""` to reference that constant so the
payload is maintained in one place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18b4bccd-8bef-4e15-a0b3-40b9540ad385

📥 Commits

Reviewing files that changed from the base of the PR and between f39d760 and 0205abb.

📒 Files selected for processing (18)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • README.md
  • app/build.gradle.kts
  • app/libs/README.md
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/gomtm/android/MainActivity.kt
  • app/src/main/java/com/gomtm/android/swarm/DiscoveredPeer.kt
  • app/src/main/java/com/gomtm/android/swarm/SwarmNodeConfig.kt
  • app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt
  • app/src/main/java/com/gomtm/android/swarm/SwarmStatus.kt
  • app/src/main/java/com/gomtm/android/swarm/SwarmUiModel.kt
  • app/src/main/res/layout/activity_main.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt
  • app/src/test/java/com/gomtm/android/swarm/SwarmUiModelTest.kt
  • docs/roadmap.md
  • docs/swarm-runtime-integration.md

Comment on lines +117 to +119
files: |
app/build/outputs/apk/release/app-release-unsigned.apk
dist/gomtm-android-provenance.json

Copy link
Copy Markdown

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
set -euo pipefail
rg -n -C3 'assembleRelease|app-release-unsigned\.apk' .github/workflows/release.yml
build_file="$(fd 'build.gradle.kts$' app | head -n1)"
[ -n "$build_file" ]
rg -n -C5 'signingConfigs|buildTypes|release' "$build_file"

Repository: codeh007/gomtm-android

Length of output: 960


🏁 Script executed:

fd 'build.gradle.kts$' app | head -n1 | xargs cat -n

Repository: codeh007/gomtm-android

Length of output: 1847


Publish a signed APK here, not app-release-unsigned.apk.

app-release-unsigned.apk is the raw unsigned output from assembleRelease. The app/build.gradle.kts release buildType has no signingConfig defined, so the GitHub release currently uploads an artifact users cannot install with the Android package manager. Add a signing configuration to the release buildType and update the workflow to reference the signed output path.

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

In @.github/workflows/release.yml around lines 117 - 119, The workflow uploads
the unsigned artifact app-release-unsigned.apk; fix by adding a signing
configuration in app module build.gradle.kts (create a signingConfigs block and
reference it from the release buildType so release.buildType uses signingConfig
release) and provide keystore credentials via secure properties/ENV (so the
Gradle release variant produces a signed APK), then update the GitHub Actions
workflow to publish the signed output (e.g., the release APK path produced by
the signed release variant such as
app/build/outputs/apk/release/app-release.apk) instead of
app-release-unsigned.apk. Ensure you reference the release buildType and
signingConfigs identifiers when making the changes.

Comment on lines +23 to +50
findViewById<MaterialButton>(R.id.startButton).setOnClickListener {
saveInputs()
runAction {
swarmRuntime.start(this, currentConfig())
}
}
findViewById<MaterialButton>(R.id.stopButton).setOnClickListener {
runAction {
swarmRuntime.stop()
}
}
findViewById<MaterialButton>(R.id.refreshButton).setOnClickListener {
saveInputs()
actionError = null
render()
}

render()
}

private fun runAction(action: () -> Unit) {
actionError = null
try {
action()
} catch (error: Exception) {
actionError = error.message ?: error.javaClass.simpleName
}
render()

Copy link
Copy Markdown

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
set -euo pipefail
file="$(fd 'SwarmRuntime\.kt$' app/src/main/java | head -n1)"
[ -n "$file" ]
rg -n -C6 'fun (start|stop|probe|drainLogs)\(' "$file"

Repository: codeh007/gomtm-android

Length of output: 1750


🏁 Script executed:

wc -l app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt

Repository: codeh007/gomtm-android

Length of output: 127


🏁 Script executed:

cat -n app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt

Repository: codeh007/gomtm-android

Length of output: 4855


🏁 Script executed:

sed -n '63,75p' app/src/main/java/com/gomtm/android/MainActivity.kt

Repository: codeh007/gomtm-android

Length of output: 800


Move probe() and drainLogs() off the main thread to prevent ANR.

Lines 23–50 and 63–75 call start(), stop(), probe(), and drainLogs() synchronously from UI event handlers. All of these methods perform reflection (Class.forName, Method.invoke) and blocking JNI calls:

  • start() does Class.forName, constructor reflection, 2 property setter invokes, file I/O (mkdirs), and a native startNode call
  • stop() does Class.forName and a native stopNode call
  • probe() chains 5 sequential JNI calls: getState, getPeerID, getBootstrapAddr, getLastError, getDiscoveredPeers
  • drainLogs() does 1 JNI call

Each time render() is called (on every button click), it executes at least 6 JNI roundtrips. Move these operations to a background dispatcher and post results back to the main thread for UI updates only.

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

In `@app/src/main/java/com/gomtm/android/MainActivity.kt` around lines 23 - 50,
The UI handlers and runAction currently call heavy/blocking methods
(swarmRuntime.start, swarmRuntime.stop, probe, drainLogs invoked via start(),
stop(), probe(), drainLogs()) on the main thread; move these calls into a
background coroutine/dispatcher (e.g., Dispatchers.IO or a dedicated
single-thread context) so reflection, file I/O and JNI round-trips run off the
UI thread, capture exceptions there, then post only the results/state back to
the main thread to update actionError and call render(); update runAction to
launch the background task and use withContext(Dispatchers.Main) or
runOnUiThread to set actionError and invoke render() so saveInputs(), render(),
and all UI updates remain on the main thread while start/stop/probe/drainLogs
execute off it.

Comment on lines +26 to +35
val item = peers.optJSONObject(index) ?: continue
add(
DiscoveredPeer(
peerId = item.optString("peer_id"),
name = item.optString("name").ifBlank { null },
state = item.optString("state", "unknown"),
discoveredInCurrentSession = item.optBoolean("discovered_in_current_session", false),
isBootstrap = item.optBoolean("is_bootstrap", false),
lastSeenAt = item.optString("last_seen_at"),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Skip malformed peer entries that have no peer_id.

optString("peer_id") returns "" when the field is missing or not a string. In app/src/main/java/com/gomtm/android/MainActivity.kt, Lines 81-90 render that verbatim, so a bad snapshot shows a blank peer block instead of being ignored.

Suggested fix
                         for (index in 0 until peers.length()) {
                             val item = peers.optJSONObject(index) ?: continue
+                            val peerId = item.optString("peer_id").trim()
+                            if (peerId.isBlank()) {
+                                continue
+                            }
                             add(
                                 DiscoveredPeer(
-                                    peerId = item.optString("peer_id"),
+                                    peerId = peerId,
                                     name = item.optString("name").ifBlank { null },
                                     state = item.optString("state", "unknown"),
                                     discoveredInCurrentSession = item.optBoolean("discovered_in_current_session", false),
📝 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
val item = peers.optJSONObject(index) ?: continue
add(
DiscoveredPeer(
peerId = item.optString("peer_id"),
name = item.optString("name").ifBlank { null },
state = item.optString("state", "unknown"),
discoveredInCurrentSession = item.optBoolean("discovered_in_current_session", false),
isBootstrap = item.optBoolean("is_bootstrap", false),
lastSeenAt = item.optString("last_seen_at"),
),
val item = peers.optJSONObject(index) ?: continue
val peerId = item.optString("peer_id").trim()
if (peerId.isBlank()) {
continue
}
add(
DiscoveredPeer(
peerId = peerId,
name = item.optString("name").ifBlank { null },
state = item.optString("state", "unknown"),
discoveredInCurrentSession = item.optBoolean("discovered_in_current_session", false),
isBootstrap = item.optBoolean("is_bootstrap", false),
lastSeenAt = item.optString("last_seen_at"),
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/gomtm/android/swarm/DiscoveredPeer.kt` around lines 26
- 35, The peer-parsing loop currently constructs DiscoveredPeer objects even
when peer_id is empty (optString returns ""), causing blank peers to render;
update the parsing in the block that reads from peers (the code creating
DiscoveredPeer instances) to first extract peerId = item.optString("peer_id")
and skip the entry if peerId is blank (continue) before calling
add(DiscoveredPeer(...)), ensuring DiscoveredPeer(peerId = peerId, ...) is only
created for non-empty peer_id values.

Comment on lines +60 to +63
private fun runtimeBaseDir(context: Context): String {
val baseDir = File(context.filesDir, "gomtm")
baseDir.mkdirs()
return baseDir.absolutePath

Copy link
Copy Markdown

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:

find . -type f -name "SwarmRuntime.kt" | head -20

Repository: codeh007/gomtm-android

Length of output: 125


🏁 Script executed:

cat -n ./app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt

Repository: codeh007/gomtm-android

Length of output: 4855


🏁 Script executed:

find . -type f -name "*SwarmRuntime*Test*" -o -type f -name "*Test*SwarmRuntime*" | head -20

Repository: codeh007/gomtm-android

Length of output: 129


🏁 Script executed:

cat -n ./app/src/test/java/com/gomtm/android/swarm/SwarmRuntimeTest.kt

Repository: codeh007/gomtm-android

Length of output: 3873


🏁 Script executed:

sed -n '74,87p' ./app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt

Repository: codeh007/gomtm-android

Length of output: 601


Fail fast if the runtime directory cannot be created.

Ignoring mkdirs() here can hand startNode() a path that does not exist, which hides the actual storage failure behind a later bridge startup error.

The suggested fix should check both whether directory creation succeeded and whether the path is actually a directory (in case a file exists at that location):

Suggested fix
     private fun runtimeBaseDir(context: Context): String {
         val baseDir = File(context.filesDir, "gomtm")
-        baseDir.mkdirs()
+        if (!baseDir.exists() && !baseDir.mkdirs()) {
+            throw IllegalStateException("Unable to create runtime directory: ${baseDir.absolutePath}")
+        }
+        if (!baseDir.isDirectory) {
+            throw IllegalStateException("Runtime path is not a directory: ${baseDir.absolutePath}")
+        }
         return baseDir.absolutePath
     }

Add a regression test where ${filesDir}/gomtm cannot be created (or is already a file) to keep the failure local to this helper rather than manifesting in the native bridge code.

📝 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
private fun runtimeBaseDir(context: Context): String {
val baseDir = File(context.filesDir, "gomtm")
baseDir.mkdirs()
return baseDir.absolutePath
private fun runtimeBaseDir(context: Context): String {
val baseDir = File(context.filesDir, "gomtm")
if (!baseDir.exists() && !baseDir.mkdirs()) {
throw IllegalStateException("Unable to create runtime directory: ${baseDir.absolutePath}")
}
if (!baseDir.isDirectory) {
throw IllegalStateException("Runtime path is not a directory: ${baseDir.absolutePath}")
}
return baseDir.absolutePath
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt` around lines 60 -
63, The runtimeBaseDir function currently ignores File.mkdirs() failures; update
runtimeBaseDir(context: Context) to check the mkdirs() return value and also
verify that the resulting File is a directory (File.isDirectory), and if either
check fails throw an informative exception (e.g., IOException or
IllegalStateException) so startNode() never receives a non-existent path; also
add a unit/instrumentation regression test that simulates filesDir/gomtm being
non-creatable (or an existing file) and asserts that runtimeBaseDir throws the
expected exception instead of allowing later bridge startup errors.

Comment on lines +66 to +70
private fun invokeString(methodName: String): String {
return try {
(invokeStatic(resolveBridgeClass(), methodName, emptyArray(), emptyArray()) as? String).orEmpty()
} catch (_: ReflectiveOperationException) {
""

Copy link
Copy Markdown

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:

# First, let's find and read the SwarmRuntime.kt file
fd -t f "SwarmRuntime.kt" --exec wc -l {} \;

Repository: codeh007/gomtm-android

Length of output: 129


🏁 Script executed:

# Read the file to understand exception handling
cat -n app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt

Repository: codeh007/gomtm-android

Length of output: 4855


🏁 Script executed:

# Also check the DiscoveredPeer.kt file mentioned in the scratchpad for context
cat -n app/src/main/java/com/gomtm/android/swarm/DiscoveredPeer.kt

Repository: codeh007/gomtm-android

Length of output: 2013


invokeString() does not shield polling from bridge method failures.

invokeStatic() wraps method execution failures in IllegalStateException, but invokeString() only catches ReflectiveOperationException. When a bridge getter like getState() or getDiscoveredPeers() throws, the IllegalStateException escapes uncaught, causing probe() and drainLogs() to crash instead of gracefully degrading.

Add a regression test where a bridge getter throws and confirm that probe() and drainLogs() continue to return safe defaults. Then update invokeString() to catch IllegalStateException:

Suggested fix
     private fun invokeString(methodName: String): String {
         return try {
             (invokeStatic(resolveBridgeClass(), methodName, emptyArray(), emptyArray()) as? String).orEmpty()
         } catch (_: ReflectiveOperationException) {
             ""
+        } catch (_: IllegalStateException) {
+            ""
         }
     }
📝 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
private fun invokeString(methodName: String): String {
return try {
(invokeStatic(resolveBridgeClass(), methodName, emptyArray(), emptyArray()) as? String).orEmpty()
} catch (_: ReflectiveOperationException) {
""
private fun invokeString(methodName: String): String {
return try {
(invokeStatic(resolveBridgeClass(), methodName, emptyArray(), emptyArray()) as? String).orEmpty()
} catch (_: ReflectiveOperationException) {
""
} catch (_: IllegalStateException) {
""
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/gomtm/android/swarm/SwarmRuntime.kt` around lines 66 -
70, invokeString currently only catches ReflectiveOperationException so
IllegalStateException thrown by invokeStatic (when bridge getters like getState
or getDiscoveredPeers throw) bubbles up and crashes probe() and drainLogs();
update invokeString to also catch IllegalStateException and return an empty
string on that exception, and add a regression test that stubs a bridge getter
to throw IllegalStateException and asserts probe() and drainLogs() return safe
defaults (empty string or expected defaults) to ensure graceful degradation;
target the invokeString, invokeStatic, probe, drainLogs, getState and
getDiscoveredPeers symbols when making changes and adding the test.

Comment on lines +8 to +16
const val VALUE_NOT_AVAILABLE = "Not available"
const val VALUE_NONE = "None"

fun from(status: SwarmStatus, actionError: String?): SwarmUiModel {
return SwarmUiModel(
connectedBootstrap = status.bootstrapAddress.ifBlank { VALUE_NOT_AVAILABLE },
errorText = actionError?.takeIf { it.isNotBlank() }
?: status.lastError.ifBlank { VALUE_NONE },
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid hardcoded user-facing fallback strings in the UI model.

Lines 8-9 introduce English-only literals that bypass Android resource localization. Prefer caller-provided localized fallback strings.

♻️ Proposed refactor
 data class SwarmUiModel(
     val connectedBootstrap: String,
     val errorText: String,
 ) {
     companion object {
-        const val VALUE_NOT_AVAILABLE = "Not available"
-        const val VALUE_NONE = "None"
-
-        fun from(status: SwarmStatus, actionError: String?): SwarmUiModel {
+        fun from(
+            status: SwarmStatus,
+            actionError: String?,
+            valueNotAvailable: String,
+            valueNone: String,
+        ): SwarmUiModel {
             return SwarmUiModel(
-                connectedBootstrap = status.bootstrapAddress.ifBlank { VALUE_NOT_AVAILABLE },
+                connectedBootstrap = status.bootstrapAddress.ifBlank { valueNotAvailable },
                 errorText = actionError?.takeIf { it.isNotBlank() }
-                    ?: status.lastError.ifBlank { VALUE_NONE },
+                    ?: status.lastError.ifBlank { valueNone },
             )
         }
     }
 }

Caller-side (outside this range) should pass getString(...) resource values when building the model.

📝 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
const val VALUE_NOT_AVAILABLE = "Not available"
const val VALUE_NONE = "None"
fun from(status: SwarmStatus, actionError: String?): SwarmUiModel {
return SwarmUiModel(
connectedBootstrap = status.bootstrapAddress.ifBlank { VALUE_NOT_AVAILABLE },
errorText = actionError?.takeIf { it.isNotBlank() }
?: status.lastError.ifBlank { VALUE_NONE },
)
const val VALUE_NOT_AVAILABLE = "Not available"
const val VALUE_NONE = "None"
fun from(
status: SwarmStatus,
actionError: String?,
valueNotAvailable: String,
valueNone: String,
): SwarmUiModel {
return SwarmUiModel(
connectedBootstrap = status.bootstrapAddress.ifBlank { valueNotAvailable },
errorText = actionError?.takeIf { it.isNotBlank() }
?: status.lastError.ifBlank { valueNone },
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/gomtm/android/swarm/SwarmUiModel.kt` around lines 8 -
16, The UI model currently uses hardcoded user-facing strings
VALUE_NOT_AVAILABLE and VALUE_NONE inside SwarmUiModel.from; remove those
English-only constants and change the API so the caller provides localized
fallback strings instead (e.g., add parameters to SwarmUiModel.from such as
notAvailableFallback and noneFallback or accept a single resource-derived pair)
and use those values when computing connectedBootstrap and errorText; update any
call sites that construct SwarmUiModel.from to pass getString(...) resource
values so localization is preserved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/release.yml (2)

74-82: Document the provenance artifact schema.

The provenance JSON schema (fields: gomtm_swarm_aar_url, gomtm_swarm_aar_version, gomtm_swarm_aar_sha256, gomtm_swarm_metadata_url, release_tag, git_commit) is not documented in docs/swarm-runtime-integration.md. Per the relevant code snippet, the documentation only covers pinning requirements but not the provenance output.

Consider adding a section documenting the provenance artifact schema for downstream consumers.

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

In @.github/workflows/release.yml around lines 74 - 82, The docs are missing a
description of the provenance artifact produced by the release script; add a new
section to docs/swarm-runtime-integration.md that documents the provenance JSON
schema produced at Path('dist/gomtm-android-provenance.json') (the provenance
dict keys: gomtm_swarm_aar_url, gomtm_swarm_aar_version, gomtm_swarm_aar_sha256,
gomtm_swarm_metadata_url, release_tag, git_commit), describe the type and
meaning of each field, example values, and any consumer expectations (e.g.,
SHA256 format and where URLs point) so downstream consumers can reliably parse
the artifact.

42-84: Consider extracting shared validation/download logic to a reusable workflow or composite action.

Lines 45-84 duplicate the validation and download logic from .github/workflows/ci.yml (lines 42-72). The only difference is the provenance generation (lines 74-82) which is release-specific.

This duplication creates maintenance burden—any fix or enhancement needs to be applied in both places.

♻️ Suggested approach

Extract the common AAR validation and download logic into a composite action (e.g., .github/actions/download-gomtm-aar/action.yml), then call it from both workflows. The release workflow can add the provenance generation as an additional step.

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

In @.github/workflows/release.yml around lines 42 - 84, The workflow duplicates
the AAR validation/download logic in the "Validate gomtm swarm AAR contract" and
"Download pinned gomtm swarm AAR and metadata" steps; extract that shared logic
into a reusable composite action (e.g., download-gomtm-aar) which takes inputs
for GOMTM_SWARM_AAR_URL, GOMTM_SWARM_METADATA_URL, GOMTM_SWARM_AAR_VERSION,
GOMTM_SWARM_AAR_SHA256, GOMTM_SWARM_AAR_NAME and GH_TOKEN and performs the
mkdir/curl/sha256sum/python validation + outputs any artifacts or metadata; then
replace the two duplicated steps in both workflows with a call to the composite
action and keep the release workflow’s provenance generation as a small separate
step that consumes the composite action’s outputs (or re-reads the downloaded
metadata) so the provenance logic remains release-specific.
🤖 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/ci.yml:
- Around line 54-56: The workflow currently exports GH_TOKEN from github.token
and uses it in curl Authorization headers to fetch GOMTM_SWARM_AAR_URL and
GOMTM_SWARM_METADATA_URL; since GITHUB_TOKEN is repo-scoped it cannot access
upstream private release assets, either switch to using a stored PAT (create a
repo secret like GOMTM_PAT and export GH_TOKEN="${{ secrets.GOMTM_PAT }}" before
the curl commands) or, if the upstream assets are public, remove the
Authorization header entirely and call curl without GH_TOKEN; update the step
around the export and the two curl invocations (referencing GOMTM_SWARM_AAR_URL,
GOMTM_SWARM_AAR_NAME, GOMTM_SWARM_METADATA_URL and GH_TOKEN) accordingly.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 74-82: The docs are missing a description of the provenance
artifact produced by the release script; add a new section to
docs/swarm-runtime-integration.md that documents the provenance JSON schema
produced at Path('dist/gomtm-android-provenance.json') (the provenance dict
keys: gomtm_swarm_aar_url, gomtm_swarm_aar_version, gomtm_swarm_aar_sha256,
gomtm_swarm_metadata_url, release_tag, git_commit), describe the type and
meaning of each field, example values, and any consumer expectations (e.g.,
SHA256 format and where URLs point) so downstream consumers can reliably parse
the artifact.
- Around line 42-84: The workflow duplicates the AAR validation/download logic
in the "Validate gomtm swarm AAR contract" and "Download pinned gomtm swarm AAR
and metadata" steps; extract that shared logic into a reusable composite action
(e.g., download-gomtm-aar) which takes inputs for GOMTM_SWARM_AAR_URL,
GOMTM_SWARM_METADATA_URL, GOMTM_SWARM_AAR_VERSION, GOMTM_SWARM_AAR_SHA256,
GOMTM_SWARM_AAR_NAME and GH_TOKEN and performs the mkdir/curl/sha256sum/python
validation + outputs any artifacts or metadata; then replace the two duplicated
steps in both workflows with a call to the composite action and keep the release
workflow’s provenance generation as a small separate step that consumes the
composite action’s outputs (or re-reads the downloaded metadata) so the
provenance logic remains release-specific.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: be7da5a3-9dea-4b27-aae2-57787879aad4

📥 Commits

Reviewing files that changed from the base of the PR and between 0205abb and 66a4ca3.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml

Comment thread .github/workflows/ci.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
.github/workflows/release.yml (1)

121-123: ⚠️ Potential issue | 🟠 Major

This asset is still unsigned.

Publishing app-release-unsigned.apk gives users a non-installable release artifact. Point the release to the signed APK only after the app's release buildType is wired to a signingConfig.

Follow-up once release signing exists
          files: |
-            app/build/outputs/apk/release/app-release-unsigned.apk
+            app/build/outputs/apk/release/app-release.apk
             dist/gomtm-android-provenance.json
For Android Gradle builds, what artifact does `assembleRelease` produce when no release `signingConfig` is set, and is `app-release-unsigned.apk` installable?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 121 - 123, The workflow is
publishing an unsigned APK (app-release-unsigned.apk) which is not installable;
update the Android build/signing so the release buildType uses a signingConfig
in your Gradle module (release signingConfig in build.gradle or a signingConfig
provided by your CI keystore) and then change the release job to publish the
signed artifact (the final signed APK/AAB produced by that signingConfig)
instead of app-release-unsigned.apk; ensure the artifact path in the workflow
(files block) points to the signed output name produced by your configured
release signing.
🤖 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/ci.yml:
- Around line 27-34: The workflow-level env defines GOMTM_RELEASE_TOKEN (and
GH_FALLBACK_TOKEN) which makes the cross-repo secret available to all subsequent
steps; remove these tokens from the top-level env block and instead add them to
the specific download step that fetches the AAR (the "download" or AAR download
step) by setting env: GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }} and
GH_FALLBACK_TOKEN: ${{ github.token }} only on that step so they are not
injected into Gradle or other PR-run steps; update the .github/workflows/ci.yml
env usage to reference only per-step env for those tokens and keep other
variables (GOMTM_SWARM_*) at workflow scope.

In @.github/workflows/release.yml:
- Around line 29-37: The repository-wide env block currently exposes
GH_FALLBACK_TOKEN and GOMTM_RELEASE_TOKEN to every job step; move these tokens
out of the global env and into the env for only the "Download pinned gomtm swarm
AAR and metadata" step (the step that needs GOMTM_SWARM_AAR_URL/metadata and the
tokens) so other steps like assembleRelease, Java setup, and Android SDK
configuration don't inherit write permissions; remove GH_FALLBACK_TOKEN and
GOMTM_RELEASE_TOKEN from the top-level env and add them as step-local env
entries for that specific download step (referencing GH_FALLBACK_TOKEN and
GOMTM_RELEASE_TOKEN names and the step title "Download pinned gomtm swarm AAR
and metadata").

---

Duplicate comments:
In @.github/workflows/release.yml:
- Around line 121-123: The workflow is publishing an unsigned APK
(app-release-unsigned.apk) which is not installable; update the Android
build/signing so the release buildType uses a signingConfig in your Gradle
module (release signingConfig in build.gradle or a signingConfig provided by
your CI keystore) and then change the release job to publish the signed artifact
(the final signed APK/AAB produced by that signingConfig) instead of
app-release-unsigned.apk; ensure the artifact path in the workflow (files block)
points to the signed output name produced by your configured release signing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0658bc9-3b67-47ee-b829-e6b39bfa1682

📥 Commits

Reviewing files that changed from the base of the PR and between 66a4ca3 and 161ac55.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml

Comment thread .github/workflows/ci.yml
Comment on lines 27 to +34
env:
GOMTM_SWARM_AAR_URL: ${{ inputs.gomtm_swarm_aar_url || vars.GOMTM_SWARM_AAR_URL }}
GOMTM_SWARM_AAR_NAME: ${{ vars.GOMTM_SWARM_AAR_NAME || 'gomtm-swarm-android.aar' }}
GOMTM_SWARM_METADATA_URL: ${{ inputs.gomtm_swarm_metadata_url || vars.GOMTM_SWARM_METADATA_URL }}
GOMTM_SWARM_AAR_VERSION: ${{ inputs.gomtm_swarm_aar_version || vars.GOMTM_SWARM_AAR_VERSION }}
GOMTM_SWARM_AAR_SHA256: ${{ inputs.gomtm_swarm_aar_sha256 || vars.GOMTM_SWARM_AAR_SHA256 }}
GOMTM_SWARM_AAR_NAME: gomtm-swarm-android.aar
GH_FALLBACK_TOKEN: ${{ github.token }}
GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Keep the cross-repo token off the PR build steps.

GOMTM_RELEASE_TOKEN is defined at workflow scope, so every later step — including the Gradle commands that run pull-request code — inherits it. That makes the cross-repo credential exfiltrable from a same-repo PR. Scope both tokens to the download step only.

Suggested fix
 env:
   GOMTM_SWARM_AAR_URL: ${{ inputs.gomtm_swarm_aar_url || vars.GOMTM_SWARM_AAR_URL }}
   GOMTM_SWARM_METADATA_URL: ${{ inputs.gomtm_swarm_metadata_url || vars.GOMTM_SWARM_METADATA_URL }}
   GOMTM_SWARM_AAR_VERSION: ${{ inputs.gomtm_swarm_aar_version || vars.GOMTM_SWARM_AAR_VERSION }}
   GOMTM_SWARM_AAR_SHA256: ${{ inputs.gomtm_swarm_aar_sha256 || vars.GOMTM_SWARM_AAR_SHA256 }}
   GOMTM_SWARM_AAR_NAME: gomtm-swarm-android.aar
-  GH_FALLBACK_TOKEN: ${{ github.token }}
-  GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}

       - name: Download pinned gomtm swarm AAR and metadata
+        env:
+          GH_FALLBACK_TOKEN: ${{ github.token }}
+          GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}
         shell: bash
In GitHub Actions, do workflow-level env variables get injected into all later `run` and `uses` steps, and are repository secrets available to `pull_request` workflows for branches in the same repository?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 27 - 34, The workflow-level env
defines GOMTM_RELEASE_TOKEN (and GH_FALLBACK_TOKEN) which makes the cross-repo
secret available to all subsequent steps; remove these tokens from the top-level
env block and instead add them to the specific download step that fetches the
AAR (the "download" or AAR download step) by setting env: GOMTM_RELEASE_TOKEN:
${{ secrets.GOMTM_GH_PAT }} and GH_FALLBACK_TOKEN: ${{ github.token }} only on
that step so they are not injected into Gradle or other PR-run steps; update the
.github/workflows/ci.yml env usage to reference only per-step env for those
tokens and keep other variables (GOMTM_SWARM_*) at workflow scope.

Comment on lines 29 to +37
env:
RELEASE_TAG: v0.2.${{ github.run_number }}
RELEASE_TAG: v0.3.${{ github.run_number }}
GOMTM_SWARM_AAR_URL: ${{ inputs.gomtm_swarm_aar_url || vars.GOMTM_SWARM_AAR_URL }}
GOMTM_SWARM_AAR_NAME: ${{ vars.GOMTM_SWARM_AAR_NAME || 'gomtm-swarm-android.aar' }}
GOMTM_SWARM_METADATA_URL: ${{ inputs.gomtm_swarm_metadata_url || vars.GOMTM_SWARM_METADATA_URL }}
GOMTM_SWARM_AAR_VERSION: ${{ inputs.gomtm_swarm_aar_version || vars.GOMTM_SWARM_AAR_VERSION }}
GOMTM_SWARM_AAR_SHA256: ${{ inputs.gomtm_swarm_aar_sha256 || vars.GOMTM_SWARM_AAR_SHA256 }}
GOMTM_SWARM_AAR_NAME: gomtm-swarm-android.aar
GH_FALLBACK_TOKEN: ${{ github.token }}
GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}

Copy link
Copy Markdown

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

🌐 Web query:

In GitHub Actions, if a job has contents: writeand a workflow-level env exposesgithub.token, can later build steps access a repo-write token via the environment?

💡 Result:

Yes, later build steps can access a repo-write token via the environment if the job has contents: write permission and the workflow-level env exposes github.token (e.g., env: GITHUB_TOKEN: ${{ github.token }}). The github.token context provides the GITHUB_TOKEN value, which inherits the job's permissions, including contents: write for repo writes like git push or API updates. Setting it in workflow-level env makes the variable available to all steps in all jobs. Steps can then use $GITHUB_TOKEN (or equivalent) for authenticated operations, such as git push or gh CLI commands. Official docs confirm github.token is equivalent to ${{ secrets.GITHUB_TOKEN }} and available in steps, with permissions controlled by the job's permissions block. Examples show passing it via env in steps for tools like gh, and the same applies at workflow level since env values propagate to steps.

Citations:


🏁 Script executed:

cat -n .github/workflows/release.yml

Repository: codeh007/gomtm-android

Length of output: 5964


Move repo-write tokens to the step that needs them.

GH_FALLBACK_TOKEN here carries contents: write, and both tokens are inherited by all steps—including assembleRelease, Java setup, and Android SDK configuration. Only the "Download pinned gomtm swarm AAR and metadata" step (line 55) actually uses them, so keep them step-local to follow least-privilege.

Suggested fix
 env:
   RELEASE_TAG: v0.3.${{ github.run_number }}
   GOMTM_SWARM_AAR_URL: ${{ inputs.gomtm_swarm_aar_url || vars.GOMTM_SWARM_AAR_URL }}
   GOMTM_SWARM_METADATA_URL: ${{ inputs.gomtm_swarm_metadata_url || vars.GOMTM_SWARM_METADATA_URL }}
   GOMTM_SWARM_AAR_VERSION: ${{ inputs.gomtm_swarm_aar_version || vars.GOMTM_SWARM_AAR_VERSION }}
   GOMTM_SWARM_AAR_SHA256: ${{ inputs.gomtm_swarm_aar_sha256 || vars.GOMTM_SWARM_AAR_SHA256 }}
   GOMTM_SWARM_AAR_NAME: gomtm-swarm-android.aar
-  GH_FALLBACK_TOKEN: ${{ github.token }}
-  GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}

       - name: Download pinned gomtm swarm AAR and metadata
+        env:
+          GH_FALLBACK_TOKEN: ${{ github.token }}
+          GOMTM_RELEASE_TOKEN: ${{ secrets.GOMTM_GH_PAT }}
         shell: bash
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 29 - 37, The repository-wide env
block currently exposes GH_FALLBACK_TOKEN and GOMTM_RELEASE_TOKEN to every job
step; move these tokens out of the global env and into the env for only the
"Download pinned gomtm swarm AAR and metadata" step (the step that needs
GOMTM_SWARM_AAR_URL/metadata and the tokens) so other steps like
assembleRelease, Java setup, and Android SDK configuration don't inherit write
permissions; remove GH_FALLBACK_TOKEN and GOMTM_RELEASE_TOKEN from the top-level
env and add them as step-local env entries for that specific download step
(referencing GH_FALLBACK_TOKEN and GOMTM_RELEASE_TOKEN names and the step title
"Download pinned gomtm swarm AAR and metadata").

@codeh007
codeh007 merged commit 6481107 into main Mar 27, 2026
1 of 2 checks passed
@codeh007
codeh007 deleted the feat/real-swarm-node-host branch March 27, 2026 20:02
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.

2 participants