Port threads-go fixes #28–#31: auth hardening + publish recovery#16
Merged
Conversation
The Go client (threads-go) received several fixes since this port last synced (Go conformance audit #24 / Rust audit PR #2). This ports the missing ones: - debug_token now calls Meta with the app access token (TH|client_id|client_secret), falling back to the user token when credentials are absent, and defaults input_token to the stored token. Meta resolves the request's app context from the caller token and rejects user tokens for dev-mode apps (Go #28). - Client::with_token falls back to /me?fields=id when debug_token fails (graph.threads.net returns HTTP 500 for valid tokens on dev-mode apps), bootstrapping TokenInfo with the user ID and a 60-day expiry (Go #29). - HTTP error classification: API error codes 190/102 map to AuthenticationError regardless of HTTP status (Meta returns 5xx for these on some endpoints), and authentication errors are never retried. Code 10 (GraphMethodException) is classified as permanent and never retried — retrying burns publish quota (Go #29, #30). - Recovery from /threads_publish false failures (Go #30/#31): when publish fails with code 10, the container may have been published anyway. The client polls the container status until PUBLISHED, then locates the post among the user's recent posts using content-based matchers (text/topic_tag/reply/quote state, children count for carousels). Matching fails closed on ambiguity or when the content has no unique discriminator, surfacing the original publish error. Not ported from Go #27 (already covered or N/A here): OAuth state CSRF (done in #14), URL log redaction (we never log query strings), the rate-limiter swap race (single shared Arc<RateLimiter> here), and the DeletePostWithConfirmation ownership fix (that API was never part of the Rust surface). Also puts the existing wiremock dev-dependency to use: new integration suite covering the debug_token caller token, the /me fallback, non-retryable auth errors, and the four recovery outcomes.
…eanups Correctness fixes found by review of the ported changes: - http.rs: parse API error code/error_subcode as u64 and clamp on conversion. Meta subcodes routinely exceed u16 (e.g. 2207026), and an out-of-range integer failed the WHOLE error-body parse — silently dropping code 190/102/10 classification and re-enabling retries for auth errors and code-10 publish failures, defeating the previous commit. Also apply code/is_transient/subcode independently of the message: an error body with a code but empty message was previously misclassified as a retryable 5xx. - types: add MediaType::Unknown with #[serde(other)]. Without it, any new media_type value from Meta failed deserialization of the entire posts response — breaking every listing call and aborting publish recovery exactly when the post exists. - client.rs: with_token no longer fabricates an AuthenticationError when the /me fallback fails; the typed /me error is propagated so callers can distinguish a transient outage from an invalid token (matches Go, which wraps with %w). An empty /me ID surfaces the original debug_token error. - recovery: own the false-failure code list (is_publish_false_failure_code) instead of aliasing the retry policy's permanent-code list — tuning "don't retry this code" must not silently start triggering recovery polls for codes that genuinely failed. Cleanups: collapse the four duplicated publish/recover blocks into publish_with_recovery; route text_matcher through the shared media_content_matches; drop a dead chrono unwrap_or fallback; single extract_base_fields pass in is_retryable_error; derive Default on the four *PostContent structs (Go zero-value ergonomics) and simplify create_quote_post and test constructors with struct-update syntax. Regression tests: subcode > u16, code-with-empty-message, unknown media_type unit + end-to-end recovery with an unknown-type post in the listing window.
Contributor
tirthpatell
force-pushed
the
feat/sync-go-fixes
branch
from
July 5, 2026 21:39
16f6d08 to
e4bdace
Compare
…otes - Bump version 0.2.0 -> 0.3.0: adding MediaType::Unknown to an exhaustive public enum is a breaking change (cargo-semver-checks enum_variant_added), consistent with the 0.1.1 -> 0.2.0 bump for the OAuth state change. - Document the /me-fallback expiry tradeoff on Client::with_token: the fallback cannot read the token's real remaining lifetime, so expires_at assumes a full 60 days; callers hitting the fallback consistently should refresh proactively. - Document that Default on the *PostContent structs yields values that fail validation (empty text/image_url/video_url/children) — required fields must always be set explicitly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Go client (threads-go) received several fixes since this Rust port last synced (at the Go conformance audit, ported here as the 32-issue audit PR). This brings the missing ones over, plus fixes from a follow-up audit of the ported code.
Ported from Go
debug_tokenapp access token (Go #28) — calls Meta withTH|client_id|client_secretas the caller token (Meta resolves app context from the caller token and rejects user tokens for dev-mode apps), falls back to the user token when credentials are absent, and defaults an emptyinput_tokento the stored token.with_token/mefallback (Go #29) — whendebug_tokenfails (graph.threads.net returns HTTP 500 for valid tokens on dev-mode apps), validates via/me?fields=idand bootstrapsTokenInfowith the user ID and a 60-day expiry.AuthenticationErrorregardless of HTTP status (Meta returns 5xx for these on some endpoints); auth errors are never retried; code 10 (GraphMethodException) is permanent and never retried, so retries don't burn publish quota.posts_publish_recoverymodule: when/threads_publishfails with code 10, the container may have been published anyway. Polls container status untilPUBLISHED, then locates the post among recent posts via content-based matchers (text, topic tag, reply parent, quote state, carousel child count). Fails closed on ambiguity or content with no unique discriminator, surfacing the original error. All fourcreate_*_postmethods are wired through it.Audit-round fixes (second commit)
u64with clamped conversion — Meta subcodes exceedu16(e.g. 2207026) and previously failed the whole body parse, silently disabling all of the classification above. Codes also apply independently of the message, so{"code":190,"message":""}no longer classifies as a retryable 5xx.MediaType::Unknownwith#[serde(other)]— a new media_type value from Meta no longer fails deserialization of an entire posts response (which would abort recovery exactly when the post exists).with_tokenerror fidelity — the typed/meerror propagates instead of a fabricated auth error, so transient outages aren't misreported as invalid tokens.publish_with_recoveryhelper replaces four copies;text_matcherroutes through the shared matcher body;Defaultderives on the*PostContentstructs.Deliberately not ported
From Go #27: OAuth state CSRF was already done here (#14); URL log redaction (this client never logs query strings); the rate-limiter swap race (single shared
Arc<RateLimiter>here); theDeletePostWithConfirmationownership fix (that API was never part of the Rust surface). Go #34 (toolchain pin) is Go-specific.Test plan
cargo fmt --check,cargo clippy --all-targets --all-features -- -D warningstests/go_parity_sync.rswiremock suite (9 tests): debug_token caller token + input_token default,/mefallback success/both-fail, auth errors not retried, recovery happy path (including an unknown media_type post in the listing window), terminal-container and ambiguous-match fail-closed, no recovery for non-code-10 errors